mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Whenever we do this, Rollup needs to materialize this as an object. This causes it to also add the Babel compatibility property which is unnecessary bloat. However, since when we use these, we leak the object this often also deopts any compiler optimizations. If we really need an object we should export default an object. Currently there is an exception for DOMTopLevelEventTypes since listing out the imports is a PITA and it doesn't escape so it should get properly inlined. We should probably move to a different pattern to avoid this for consistency though.
318 lines
9.8 KiB
JavaScript
318 lines
9.8 KiB
JavaScript
/**
|
|
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
*/
|
|
|
|
import type {Fiber} from './ReactFiber';
|
|
import type {StackCursor} from './ReactFiberStack';
|
|
|
|
import {isFiberMounted} from 'react-reconciler/reflection';
|
|
import {ClassComponent, HostRoot} from 'shared/ReactWorkTags';
|
|
import getComponentName from 'shared/getComponentName';
|
|
import invariant from 'shared/invariant';
|
|
import warningWithoutStack from 'shared/warningWithoutStack';
|
|
import checkPropTypes from 'prop-types/checkPropTypes';
|
|
|
|
import {setCurrentPhase, getCurrentFiberStackInDev} from './ReactCurrentFiber';
|
|
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
|
|
import {createCursor, push, pop} from './ReactFiberStack';
|
|
|
|
let warnedAboutMissingGetChildContext;
|
|
|
|
if (__DEV__) {
|
|
warnedAboutMissingGetChildContext = {};
|
|
}
|
|
|
|
export const emptyContextObject = {};
|
|
if (__DEV__) {
|
|
Object.freeze(emptyContextObject);
|
|
}
|
|
|
|
// A cursor to the current merged context object on the stack.
|
|
let contextStackCursor: StackCursor<Object> = createCursor(emptyContextObject);
|
|
// 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 = emptyContextObject;
|
|
|
|
function getUnmaskedContext(
|
|
workInProgress: Fiber,
|
|
Component: Function,
|
|
didPushOwnContextIfProvider: boolean,
|
|
): Object {
|
|
if (didPushOwnContextIfProvider && isContextProvider(Component)) {
|
|
// If the fiber is a context provider itself, when we read its context
|
|
// we may 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;
|
|
}
|
|
|
|
function cacheContext(
|
|
workInProgress: Fiber,
|
|
unmaskedContext: Object,
|
|
maskedContext: Object,
|
|
): void {
|
|
const instance = workInProgress.stateNode;
|
|
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
|
|
instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
|
|
}
|
|
|
|
function getMaskedContext(
|
|
workInProgress: Fiber,
|
|
unmaskedContext: Object,
|
|
): Object {
|
|
const type = workInProgress.type;
|
|
const contextTypes = type.contextTypes;
|
|
if (!contextTypes) {
|
|
return emptyContextObject;
|
|
}
|
|
|
|
// 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(type) || 'Unknown';
|
|
checkPropTypes(
|
|
contextTypes,
|
|
context,
|
|
'context',
|
|
name,
|
|
getCurrentFiberStackInDev,
|
|
);
|
|
}
|
|
|
|
// 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;
|
|
}
|
|
|
|
function hasContextChanged(): boolean {
|
|
return didPerformWorkStackCursor.current;
|
|
}
|
|
|
|
function isContextProvider(type: Function): boolean {
|
|
const childContextTypes = type.childContextTypes;
|
|
return childContextTypes !== null && childContextTypes !== undefined;
|
|
}
|
|
|
|
function popContext(fiber: Fiber): void {
|
|
pop(didPerformWorkStackCursor, fiber);
|
|
pop(contextStackCursor, fiber);
|
|
}
|
|
|
|
function popTopLevelContextObject(fiber: Fiber): void {
|
|
pop(didPerformWorkStackCursor, fiber);
|
|
pop(contextStackCursor, fiber);
|
|
}
|
|
|
|
function pushTopLevelContextObject(
|
|
fiber: Fiber,
|
|
context: Object,
|
|
didChange: boolean,
|
|
): void {
|
|
invariant(
|
|
contextStackCursor.current === emptyContextObject,
|
|
'Unexpected context found on stack. ' +
|
|
'This error is likely caused by a bug in React. Please file an issue.',
|
|
);
|
|
|
|
push(contextStackCursor, context, fiber);
|
|
push(didPerformWorkStackCursor, didChange, fiber);
|
|
}
|
|
|
|
function processChildContext(
|
|
fiber: Fiber,
|
|
type: any,
|
|
parentContext: Object,
|
|
): Object {
|
|
const instance = fiber.stateNode;
|
|
const childContextTypes = 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(type) || 'Unknown';
|
|
|
|
if (!warnedAboutMissingGetChildContext[componentName]) {
|
|
warnedAboutMissingGetChildContext[componentName] = true;
|
|
warningWithoutStack(
|
|
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;
|
|
}
|
|
|
|
let childContext;
|
|
if (__DEV__) {
|
|
setCurrentPhase('getChildContext');
|
|
}
|
|
startPhaseTimer(fiber, 'getChildContext');
|
|
childContext = instance.getChildContext();
|
|
stopPhaseTimer();
|
|
if (__DEV__) {
|
|
setCurrentPhase(null);
|
|
}
|
|
for (let contextKey in childContext) {
|
|
invariant(
|
|
contextKey in childContextTypes,
|
|
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
|
|
getComponentName(type) || 'Unknown',
|
|
contextKey,
|
|
);
|
|
}
|
|
if (__DEV__) {
|
|
const name = getComponentName(type) || 'Unknown';
|
|
checkPropTypes(
|
|
childContextTypes,
|
|
childContext,
|
|
'child context',
|
|
name,
|
|
// In practice, there is one case in which we won't get a stack. It's when
|
|
// somebody calls unstable_renderSubtreeIntoContainer() and we process
|
|
// context from the parent component instance. The stack will be missing
|
|
// because it's outside of the reconciliation, and so the pointer has not
|
|
// been set. This is rare and doesn't matter. We'll also remove that API.
|
|
getCurrentFiberStackInDev,
|
|
);
|
|
}
|
|
|
|
return {...parentContext, ...childContext};
|
|
}
|
|
|
|
function pushContextProvider(workInProgress: Fiber): boolean {
|
|
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) ||
|
|
emptyContextObject;
|
|
|
|
// Remember the parent context so we can merge with it later.
|
|
// Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
|
|
previousContext = contextStackCursor.current;
|
|
push(contextStackCursor, memoizedMergedChildContext, workInProgress);
|
|
push(
|
|
didPerformWorkStackCursor,
|
|
didPerformWorkStackCursor.current,
|
|
workInProgress,
|
|
);
|
|
|
|
return true;
|
|
}
|
|
|
|
function invalidateContextProvider(
|
|
workInProgress: Fiber,
|
|
type: any,
|
|
didChange: boolean,
|
|
): void {
|
|
const instance = workInProgress.stateNode;
|
|
invariant(
|
|
instance,
|
|
'Expected to have an instance by this point. ' +
|
|
'This error is likely caused by a bug in React. Please file an issue.',
|
|
);
|
|
|
|
if (didChange) {
|
|
// Merge parent and own context.
|
|
// Skip this if we're not updating due to sCU.
|
|
// This avoids unnecessarily recomputing memoized values.
|
|
const mergedContext = processChildContext(
|
|
workInProgress,
|
|
type,
|
|
previousContext,
|
|
);
|
|
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, didChange, workInProgress);
|
|
} else {
|
|
pop(didPerformWorkStackCursor, workInProgress);
|
|
push(didPerformWorkStackCursor, didChange, workInProgress);
|
|
}
|
|
}
|
|
|
|
function findCurrentUnmaskedContext(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. ' +
|
|
'This error is likely caused by a bug in React. Please file an issue.',
|
|
);
|
|
|
|
let node = fiber;
|
|
do {
|
|
switch (node.tag) {
|
|
case HostRoot:
|
|
return node.stateNode.context;
|
|
case ClassComponent: {
|
|
const Component = node.type;
|
|
if (isContextProvider(Component)) {
|
|
return node.stateNode.__reactInternalMemoizedMergedChildContext;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
node = node.return;
|
|
} while (node !== null);
|
|
invariant(
|
|
false,
|
|
'Found unexpected detached subtree parent. ' +
|
|
'This error is likely caused by a bug in React. Please file an issue.',
|
|
);
|
|
}
|
|
|
|
export {
|
|
getUnmaskedContext,
|
|
cacheContext,
|
|
getMaskedContext,
|
|
hasContextChanged,
|
|
popContext,
|
|
popTopLevelContextObject,
|
|
pushTopLevelContextObject,
|
|
processChildContext,
|
|
isContextProvider,
|
|
pushContextProvider,
|
|
invalidateContextProvider,
|
|
findCurrentUnmaskedContext,
|
|
};
|