react-native
Version:
A framework for building native apps using React
260 lines (225 loc) • 9.24 kB
JavaScript
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule ReactFiberContext
* @flow
*/
;
import type { Fiber } from 'ReactFiber';
import type { StackCursor } from 'ReactFiberStack';
var emptyObject = require('fbjs/lib/emptyObject');
var invariant = require('fbjs/lib/invariant');
var warning = require('fbjs/lib/warning');
var {
getComponentName,
isFiberMounted,
} = require('ReactFiberTreeReflection');
var {
ClassComponent,
HostRoot,
} = require('ReactTypeOfWork');
const {
createCursor,
pop,
push,
} = require('ReactFiberStack');
if (__DEV__) {
var checkReactTypeSpec = require('checkReactTypeSpec');
var ReactDebugCurrentFrame = require('react/lib/ReactDebugCurrentFrame');
var warnedAboutMissingGetChildContext = {};
}
// A cursor to the current merged context object on the stack.
let contextStackCursor : StackCursor<Object> = createCursor(emptyObject);
// A cursor to a boolean indicating whether the context has changed.
let didPerformWorkStackCursor : StackCursor<boolean> = createCursor(false);
// Keep track of the previous context object that was on the stack.
// We use this to get access to the parent context after we have already
// pushed the next context provider, and now need to merge their contexts.
let previousContext : Object = emptyObject;
function getUnmaskedContext(workInProgress : Fiber) : Object {
const hasOwnContext = isContextProvider(workInProgress);
if (hasOwnContext) {
// If the fiber is a context provider itself, when we read its context
// we have already pushed its own child context on the stack. A context
// provider should not "see" its own child context. Therefore we read the
// previous (parent) context instead for a context provider.
return previousContext;
}
return contextStackCursor.current;
}
exports.getUnmaskedContext = getUnmaskedContext;
function cacheContext(workInProgress : Fiber, unmaskedContext : Object, maskedContext : Object) {
const instance = workInProgress.stateNode;
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
}
exports.cacheContext = cacheContext;
exports.getMaskedContext = function(workInProgress : Fiber, unmaskedContext : Object) {
const type = workInProgress.type;
const contextTypes = type.contextTypes;
if (!contextTypes) {
return emptyObject;
}
// Avoid recreating masked context unless unmasked context has changed.
// Failing to do this will result in unnecessary calls to componentWillReceiveProps.
// This may trigger infinite loops if componentWillReceiveProps calls setState.
const instance = workInProgress.stateNode;
if (
instance &&
instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
) {
return instance.__reactInternalMemoizedMaskedChildContext;
}
const context = {};
for (let key in contextTypes) {
context[key] = unmaskedContext[key];
}
if (__DEV__) {
const name = getComponentName(workInProgress);
ReactDebugCurrentFrame.current = workInProgress;
checkReactTypeSpec(contextTypes, context, 'context', name);
ReactDebugCurrentFrame.current = null;
}
// Cache unmasked context so we can avoid recreating masked context unless necessary.
// Context is created before the class component is instantiated so check for instance.
if (instance) {
cacheContext(workInProgress, unmaskedContext, context);
}
return context;
};
exports.hasContextChanged = function() : boolean {
return didPerformWorkStackCursor.current;
};
function isContextConsumer(fiber : Fiber) : boolean {
return (
fiber.tag === ClassComponent &&
fiber.type.contextTypes != null
);
}
exports.isContextConsumer = isContextConsumer;
function isContextProvider(fiber : Fiber) : boolean {
return (
fiber.tag === ClassComponent &&
fiber.type.childContextTypes != null
);
}
exports.isContextProvider = isContextProvider;
function popContextProvider(fiber : Fiber) : void {
if (!isContextProvider(fiber)) {
return;
}
pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}
exports.popContextProvider = popContextProvider;
exports.pushTopLevelContextObject = function(fiber : Fiber, context : Object, didChange : boolean) : void {
invariant(contextStackCursor.cursor == null, 'Unexpected context found on stack');
push(contextStackCursor, context, fiber);
push(didPerformWorkStackCursor, didChange, fiber);
};
function processChildContext(fiber : Fiber, parentContext : Object, isReconciling : boolean): Object {
const instance = fiber.stateNode;
const childContextTypes = fiber.type.childContextTypes;
// TODO (bvaughn) Replace this behavior with an invariant() in the future.
// It has only been added in Fiber to match the (unintentional) behavior in Stack.
if (typeof instance.getChildContext !== 'function') {
if (__DEV__) {
const componentName = getComponentName(fiber);
if (!warnedAboutMissingGetChildContext[componentName]) {
warnedAboutMissingGetChildContext[componentName] = true;
warning(
false,
'%s.childContextTypes is specified but there is no getChildContext() method ' +
'on the instance. You can either define getChildContext() on %s or remove ' +
'childContextTypes from it.',
componentName,
componentName,
);
}
}
return parentContext;
}
const childContext = instance.getChildContext();
for (let contextKey in childContext) {
invariant(
contextKey in childContextTypes,
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
getComponentName(fiber),
contextKey
);
}
if (__DEV__) {
const name = getComponentName(fiber);
// We can only provide accurate element stacks if we pass work-in-progress tree
// during the begin or complete phase. However currently this function is also
// called from unstable_renderSubtree legacy implementation. In this case it unsafe to
// assume anything about the given fiber. We won't pass it down if we aren't sure.
// TODO: remove this hack when we delete unstable_renderSubtree in Fiber.
const workInProgress = isReconciling ? fiber : null;
ReactDebugCurrentFrame.current = workInProgress;
checkReactTypeSpec(childContextTypes, childContext, 'child context', name);
ReactDebugCurrentFrame.current = null;
}
return {...parentContext, ...childContext};
}
exports.processChildContext = processChildContext;
exports.pushContextProvider = function(workInProgress : Fiber) : boolean {
if (!isContextProvider(workInProgress)) {
return false;
}
const instance = workInProgress.stateNode;
// We push the context as early as possible to ensure stack integrity.
// If the instance does not exist yet, we will push null at first,
// and replace it on the stack later when invalidating the context.
const memoizedMergedChildContext = (
instance &&
instance.__reactInternalMemoizedMergedChildContext
) || emptyObject;
// Remember the parent context so we can merge with it later.
previousContext = contextStackCursor.current;
push(contextStackCursor, memoizedMergedChildContext, workInProgress);
push(didPerformWorkStackCursor, false, workInProgress);
return true;
};
exports.invalidateContextProvider = function(workInProgress : Fiber) : void {
const instance = workInProgress.stateNode;
invariant(instance, 'Expected to have an instance by this point.');
// Merge parent and own context.
const mergedContext = processChildContext(workInProgress, previousContext, true);
instance.__reactInternalMemoizedMergedChildContext = mergedContext;
// Replace the old (or empty) context with the new one.
// It is important to unwind the context in the reverse order.
pop(didPerformWorkStackCursor, workInProgress);
pop(contextStackCursor, workInProgress);
// Now push the new context and mark that it has changed.
push(contextStackCursor, mergedContext, workInProgress);
push(didPerformWorkStackCursor, true, workInProgress);
};
exports.resetContext = function() : void {
previousContext = emptyObject;
contextStackCursor.current = emptyObject;
didPerformWorkStackCursor.current = false;
};
exports.findCurrentUnmaskedContext = function(fiber: Fiber) : Object {
// Currently this is only used with renderSubtreeIntoContainer; not sure if it
// makes sense elsewhere
invariant(
isFiberMounted(fiber) && fiber.tag === ClassComponent,
'Expected subtree parent to be a mounted class component'
);
let node : Fiber = fiber;
while (node.tag !== HostRoot) {
if (isContextProvider(node)) {
return node.stateNode.__reactInternalMemoizedMergedChildContext;
}
const parent = node.return;
invariant(parent, 'Found unexpected detached subtree parent');
node = parent;
}
return node.stateNode.context;
};