Unify context stack implementations (#12359)

* Use module pattern so context stack is isolated per renderer

* Unify context implementations

Implements the new context API on top of the existing ReactStack that we
already use for host context and legacy context. Now there is a single
array that we push and pop from.

This makes the interrupt path slightly slower, since when we reset the
unit of work pointer, we have to iterate over the stack (like before)
*and* switch on the type of work (not like before). On the other hand,
this unifies all of the unwinding behavior in the UnwindWork module.

* Add DEV only warning if stack is not reset properly
This commit is contained in:
Andrew Clark 2018-03-15 19:27:44 -07:00 committed by GitHub
parent 2738e84805
commit 208b490ed9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 568 additions and 433 deletions

View File

@ -11,6 +11,8 @@ import type {HostConfig} from 'react-reconciler';
import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
import type {Fiber} from 'react-reconciler/src/ReactFiber';
import type {HostContext} from './ReactFiberHostContext';
import type {LegacyContext} from './ReactFiberContext';
import type {NewContext} from './ReactFiberNewContext';
import type {HydrationContext} from './ReactFiberHydrationContext';
import type {FiberRoot} from './ReactFiberRoot';
import type {ExpirationTime} from './ReactFiberExpirationTime';
@ -57,15 +59,6 @@ import {
cloneChildFibers,
} from './ReactChildFiber';
import {processUpdateQueue} from './ReactFiberUpdateQueue';
import {
getMaskedContext,
getUnmaskedContext,
hasContextChanged as hasLegacyContextChanged,
pushContextProvider as pushLegacyContextProvider,
pushTopLevelContextObject,
invalidateContextProvider,
} from './ReactFiberContext';
import {pushProvider} from './ReactFiberNewContext';
import {NoWork, Never} from './ReactFiberExpirationTime';
import {AsyncMode, StrictMode} from './ReactTypeOfMode';
import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
@ -83,6 +76,8 @@ if (__DEV__) {
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
hostContext: HostContext<C, CX>,
legacyContext: LegacyContext,
newContext: NewContext,
hydrationContext: HydrationContext<C, CX>,
scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
computeExpirationForFiber: (fiber: Fiber) => ExpirationTime,
@ -91,6 +86,17 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
const {pushHostContext, pushHostContainer} = hostContext;
const {pushProvider} = newContext;
const {
getMaskedContext,
getUnmaskedContext,
hasContextChanged: hasLegacyContextChanged,
pushContextProvider: pushLegacyContextProvider,
pushTopLevelContextObject,
invalidateContextProvider,
} = legacyContext;
const {
enterHydrationState,
resetHydrationState,
@ -105,6 +111,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
resumeMountClassInstance,
updateClassInstance,
} = ReactFiberClassComponent(
legacyContext,
scheduleWork,
computeExpirationForFiber,
memoizeProps,

View File

@ -9,6 +9,7 @@
import type {Fiber} from './ReactFiber';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {LegacyContext} from './ReactFiberContext';
import type {CapturedValue} from './ReactCapturedValue';
import {Update} from 'shared/ReactTypeOfSideEffect';
@ -29,17 +30,10 @@ import warning from 'fbjs/lib/warning';
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
import {StrictMode} from './ReactTypeOfMode';
import {
cacheContext,
getMaskedContext,
getUnmaskedContext,
isContextConsumer,
} from './ReactFiberContext';
import {
insertUpdateIntoFiber,
processUpdateQueue,
} from './ReactFiberUpdateQueue';
import {hasContextChanged} from './ReactFiberContext';
const fakeInternalInstance = {};
const isArray = Array.isArray;
@ -110,11 +104,20 @@ function callGetDerivedStateFromCatch(ctor: any, capturedValues: Array<mixed>) {
}
export default function(
legacyContext: LegacyContext,
scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
computeExpirationForFiber: (fiber: Fiber) => ExpirationTime,
memoizeProps: (workInProgress: Fiber, props: any) => void,
memoizeState: (workInProgress: Fiber, state: any) => void,
) {
const {
cacheContext,
getMaskedContext,
getUnmaskedContext,
isContextConsumer,
hasContextChanged,
} = legacyContext;
// Class component state updater
const updater = {
isMounted,

View File

@ -11,6 +11,8 @@ import type {HostConfig} from 'react-reconciler';
import type {Fiber} from './ReactFiber';
import type {ExpirationTime} from './ReactFiberExpirationTime';
import type {HostContext} from './ReactFiberHostContext';
import type {LegacyContext} from './ReactFiberContext';
import type {NewContext} from './ReactFiberNewContext';
import type {HydrationContext} from './ReactFiberHydrationContext';
import type {FiberRoot} from './ReactFiberRoot';
@ -46,15 +48,12 @@ import {
import invariant from 'fbjs/lib/invariant';
import {reconcileChildFibers} from './ReactChildFiber';
import {
popContextProvider as popLegacyContextProvider,
popTopLevelContextObject as popTopLevelLegacyContextObject,
} from './ReactFiberContext';
import {popProvider} from './ReactFiberNewContext';
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
hostContext: HostContext<C, CX>,
legacyContext: LegacyContext,
newContext: NewContext,
hydrationContext: HydrationContext<C, CX>,
) {
const {
@ -74,6 +73,13 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
popHostContainer,
} = hostContext;
const {
popContextProvider: popLegacyContextProvider,
popTopLevelContextObject: popTopLevelLegacyContextObject,
} = legacyContext;
const {popProvider} = newContext;
const {
prepareToHydrateHostInstance,
prepareToHydrateHostTextInstance,

View File

@ -8,7 +8,7 @@
*/
import type {Fiber} from './ReactFiber';
import type {StackCursor} from './ReactFiberStack';
import type {StackCursor, Stack} from './ReactFiberStack';
import {isFiberMounted} from 'react-reconciler/reflection';
import {ClassComponent, HostRoot} from 'shared/ReactTypeOfWork';
@ -18,7 +18,6 @@ import invariant from 'fbjs/lib/invariant';
import warning from 'fbjs/lib/warning';
import checkPropTypes from 'prop-types/checkPropTypes';
import {createCursor, pop, push} from './ReactFiberStack';
import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
@ -28,273 +27,308 @@ if (__DEV__) {
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;
export type LegacyContext = {
getUnmaskedContext(workInProgress: Fiber): Object,
cacheContext(
workInProgress: Fiber,
unmaskedContext: Object,
maskedContext: Object,
): void,
getMaskedContext(workInProgress: Fiber, unmaskedContext: Object): Object,
hasContextChanged(): boolean,
isContextConsumer(fiber: Fiber): boolean,
isContextProvider(fiber: Fiber): boolean,
popContextProvider(fiber: Fiber): void,
popTopLevelContextObject(fiber: Fiber): void,
pushTopLevelContextObject(
fiber: Fiber,
context: Object,
didChange: boolean,
): void,
processChildContext(fiber: Fiber, parentContext: Object): Object,
pushContextProvider(workInProgress: Fiber): boolean,
invalidateContextProvider(workInProgress: Fiber, didChange: boolean): void,
findCurrentUnmaskedContext(fiber: Fiber): Object,
};
export 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;
}
export default function(stack: Stack): LegacyContext {
const {createCursor, push, pop} = stack;
export function cacheContext(
workInProgress: Fiber,
unmaskedContext: Object,
maskedContext: Object,
) {
const instance = workInProgress.stateNode;
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
}
// 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;
export function getMaskedContext(
workInProgress: Fiber,
unmaskedContext: Object,
) {
const type = workInProgress.type;
const contextTypes = type.contextTypes;
if (!contextTypes) {
return 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;
}
// 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
function cacheContext(
workInProgress: Fiber,
unmaskedContext: Object,
maskedContext: Object,
) {
return instance.__reactInternalMemoizedMaskedChildContext;
const instance = workInProgress.stateNode;
instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
}
const context = {};
for (let key in contextTypes) {
context[key] = unmaskedContext[key];
}
function getMaskedContext(workInProgress: Fiber, unmaskedContext: Object) {
const type = workInProgress.type;
const contextTypes = type.contextTypes;
if (!contextTypes) {
return emptyObject;
}
if (__DEV__) {
const name = getComponentName(workInProgress) || 'Unknown';
checkPropTypes(
contextTypes,
context,
'context',
name,
ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
);
}
// 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;
}
// 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);
}
const context = {};
for (let key in contextTypes) {
context[key] = unmaskedContext[key];
}
return context;
}
export function hasContextChanged(): boolean {
return didPerformWorkStackCursor.current;
}
export function isContextConsumer(fiber: Fiber): boolean {
return fiber.tag === ClassComponent && fiber.type.contextTypes != null;
}
export function isContextProvider(fiber: Fiber): boolean {
return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;
}
export function popContextProvider(fiber: Fiber): void {
if (!isContextProvider(fiber)) {
return;
}
pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}
export function popTopLevelContextObject(fiber: Fiber) {
pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}
export function pushTopLevelContextObject(
fiber: Fiber,
context: Object,
didChange: boolean,
): void {
invariant(
contextStackCursor.cursor == null,
'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);
}
export function processChildContext(
fiber: Fiber,
parentContext: Object,
): 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) || 'Unknown';
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,
);
}
const name = getComponentName(workInProgress) || 'Unknown';
checkPropTypes(
contextTypes,
context,
'context',
name,
ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
);
}
return parentContext;
}
let childContext;
if (__DEV__) {
ReactDebugCurrentFiber.setCurrentPhase('getChildContext');
}
startPhaseTimer(fiber, 'getChildContext');
childContext = instance.getChildContext();
stopPhaseTimer();
if (__DEV__) {
ReactDebugCurrentFiber.setCurrentPhase(null);
}
for (let contextKey in childContext) {
invariant(
contextKey in childContextTypes,
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
getComponentName(fiber) || 'Unknown',
contextKey,
);
}
if (__DEV__) {
const name = getComponentName(fiber) || '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.
ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
);
}
return {...parentContext, ...childContext};
}
export function pushContextProvider(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.
// 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;
}
export function invalidateContextProvider(
workInProgress: Fiber,
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, 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);
}
}
export function resetContext(): void {
previousContext = emptyObject;
contextStackCursor.current = emptyObject;
didPerformWorkStackCursor.current = false;
}
export 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 = fiber;
while (node.tag !== HostRoot) {
if (isContextProvider(node)) {
return node.stateNode.__reactInternalMemoizedMergedChildContext;
// 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);
}
const parent = node.return;
return context;
}
function hasContextChanged(): boolean {
return didPerformWorkStackCursor.current;
}
function isContextConsumer(fiber: Fiber): boolean {
return fiber.tag === ClassComponent && fiber.type.contextTypes != null;
}
function isContextProvider(fiber: Fiber): boolean {
return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;
}
function popContextProvider(fiber: Fiber): void {
if (!isContextProvider(fiber)) {
return;
}
pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}
function popTopLevelContextObject(fiber: Fiber) {
pop(didPerformWorkStackCursor, fiber);
pop(contextStackCursor, fiber);
}
function pushTopLevelContextObject(
fiber: Fiber,
context: Object,
didChange: boolean,
): void {
invariant(
parent,
'Found unexpected detached subtree parent. ' +
contextStackCursor.cursor == null,
'Unexpected context found on stack. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
node = parent;
push(contextStackCursor, context, fiber);
push(didPerformWorkStackCursor, didChange, fiber);
}
return node.stateNode.context;
function processChildContext(fiber: Fiber, parentContext: Object): 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) || 'Unknown';
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;
}
let childContext;
if (__DEV__) {
ReactDebugCurrentFiber.setCurrentPhase('getChildContext');
}
startPhaseTimer(fiber, 'getChildContext');
childContext = instance.getChildContext();
stopPhaseTimer();
if (__DEV__) {
ReactDebugCurrentFiber.setCurrentPhase(null);
}
for (let contextKey in childContext) {
invariant(
contextKey in childContextTypes,
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
getComponentName(fiber) || 'Unknown',
contextKey,
);
}
if (__DEV__) {
const name = getComponentName(fiber) || '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.
ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
);
}
return {...parentContext, ...childContext};
}
function pushContextProvider(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.
// 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,
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,
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 = fiber;
while (node.tag !== HostRoot) {
if (isContextProvider(node)) {
return node.stateNode.__reactInternalMemoizedMergedChildContext;
}
const parent = node.return;
invariant(
parent,
'Found unexpected detached subtree parent. ' +
'This error is likely caused by a bug in React. Please file an issue.',
);
node = parent;
}
return node.stateNode.context;
}
return {
getUnmaskedContext,
cacheContext,
getMaskedContext,
hasContextChanged,
isContextConsumer,
isContextProvider,
popContextProvider,
popTopLevelContextObject,
pushTopLevelContextObject,
processChildContext,
pushContextProvider,
invalidateContextProvider,
findCurrentUnmaskedContext,
};
}

View File

@ -9,12 +9,10 @@
import type {HostConfig} from 'react-reconciler';
import type {Fiber} from './ReactFiber';
import type {StackCursor} from './ReactFiberStack';
import type {StackCursor, Stack} from './ReactFiberStack';
import invariant from 'fbjs/lib/invariant';
import {createCursor, pop, push} from './ReactFiberStack';
declare class NoContextT {}
const NO_CONTEXT: NoContextT = ({}: any);
@ -25,13 +23,14 @@ export type HostContext<C, CX> = {
popHostContext(fiber: Fiber): void,
pushHostContainer(fiber: Fiber, container: C): void,
pushHostContext(fiber: Fiber): void,
resetHostContainer(): void,
};
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
stack: Stack,
): HostContext<C, CX> {
const {getChildHostContext, getRootHostContext} = config;
const {createCursor, push, pop} = stack;
let contextStackCursor: StackCursor<CX | NoContextT> = createCursor(
NO_CONTEXT,
@ -108,11 +107,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
pop(contextFiberStackCursor, fiber);
}
function resetHostContainer() {
contextStackCursor.current = NO_CONTEXT;
rootInstanceStackCursor.current = NO_CONTEXT;
}
return {
getHostContext,
getRootHostContainer,
@ -120,6 +114,5 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
popHostContext,
pushHostContainer,
pushHostContext,
resetHostContainer,
};
}

View File

@ -9,67 +9,64 @@
import type {Fiber} from './ReactFiber';
import type {ReactContext} from 'shared/ReactTypes';
import type {StackCursor, Stack} from './ReactFiberStack';
import warning from 'fbjs/lib/warning';
let changedBitsStack: Array<any> = [];
let currentValueStack: Array<any> = [];
let stack: Array<Fiber> = [];
let index = -1;
export type NewContext = {
pushProvider(providerFiber: Fiber): void,
popProvider(providerFiber: Fiber): void,
};
let rendererSigil;
if (__DEV__) {
// Use this to detect multiple renderers using the same context
rendererSigil = {};
}
export default function(stack: Stack) {
const {createCursor, push, pop} = stack;
export function pushProvider(providerFiber: Fiber): void {
const context: ReactContext<any> = providerFiber.type.context;
index += 1;
changedBitsStack[index] = context._changedBits;
currentValueStack[index] = context._currentValue;
stack[index] = providerFiber;
context._currentValue = providerFiber.pendingProps.value;
context._changedBits = providerFiber.stateNode;
const providerCursor: StackCursor<Fiber | null> = createCursor(null);
const valueCursor: StackCursor<mixed> = createCursor(null);
const changedBitsCursor: StackCursor<number> = createCursor(0);
let rendererSigil;
if (__DEV__) {
warning(
context._currentRenderer === null ||
context._currentRenderer === rendererSigil,
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
context._currentRenderer = rendererSigil;
// Use this to detect multiple renderers using the same context
rendererSigil = {};
}
}
export function popProvider(providerFiber: Fiber): void {
if (__DEV__) {
warning(index > -1 && providerFiber === stack[index], 'Unexpected pop.');
}
const changedBits = changedBitsStack[index];
const currentValue = currentValueStack[index];
changedBitsStack[index] = null;
currentValueStack[index] = null;
stack[index] = null;
index -= 1;
const context: ReactContext<any> = providerFiber.type.context;
context._currentValue = currentValue;
context._changedBits = changedBits;
}
export function resetProviderStack(): void {
for (let i = index; i > -1; i--) {
const providerFiber = stack[i];
function pushProvider(providerFiber: Fiber): void {
const context: ReactContext<any> = providerFiber.type.context;
context._currentValue = context._defaultValue;
context._changedBits = 0;
changedBitsStack[i] = null;
currentValueStack[i] = null;
stack[i] = null;
push(changedBitsCursor, context._changedBits, providerFiber);
push(valueCursor, context._currentValue, providerFiber);
push(providerCursor, providerFiber, providerFiber);
context._currentValue = providerFiber.pendingProps.value;
context._changedBits = providerFiber.stateNode;
if (__DEV__) {
context._currentRenderer = null;
warning(
context._currentRenderer === null ||
context._currentRenderer === rendererSigil,
'Detected multiple renderers concurrently rendering the ' +
'same context provider. This is currently unsupported.',
);
context._currentRenderer = rendererSigil;
}
}
index = -1;
function popProvider(providerFiber: Fiber): void {
const changedBits = changedBitsCursor.current;
const currentValue = valueCursor.current;
pop(providerCursor, providerFiber);
pop(valueCursor, providerFiber);
pop(changedBitsCursor, providerFiber);
const context: ReactContext<any> = providerFiber.type.context;
context._currentValue = currentValue;
context._changedBits = changedBits;
}
return {
pushProvider,
popProvider,
};
}

View File

@ -22,11 +22,6 @@ import emptyObject from 'fbjs/lib/emptyObject';
import getComponentName from 'shared/getComponentName';
import warning from 'fbjs/lib/warning';
import {
findCurrentUnmaskedContext,
isContextProvider,
processChildContext,
} from './ReactFiberContext';
import {createFiberRoot} from './ReactFiberRoot';
import * as ReactFiberDevToolsHook from './ReactFiberDevToolsHook';
import ReactFiberScheduler from './ReactFiberScheduler';
@ -274,20 +269,6 @@ export type Reconciler<C, I, TI> = {
findHostInstanceWithNoPortals(component: Fiber): I | TI | null,
};
function getContextForSubtree(
parentComponent: ?React$Component<any, any>,
): Object {
if (!parentComponent) {
return emptyObject;
}
const fiber = ReactInstanceMap.get(parentComponent);
const parentContext = findCurrentUnmaskedContext(fiber);
return isContextProvider(fiber)
? processChildContext(fiber, parentContext)
: parentContext;
}
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
): Reconciler<C, I, TI> {
@ -308,8 +289,29 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
syncUpdates,
interactiveUpdates,
flushInteractiveUpdates,
legacyContext,
} = ReactFiberScheduler(config);
const {
findCurrentUnmaskedContext,
isContextProvider,
processChildContext,
} = legacyContext;
function getContextForSubtree(
parentComponent: ?React$Component<any, any>,
): Object {
if (!parentComponent) {
return emptyObject;
}
const fiber = ReactInstanceMap.get(parentComponent);
const parentContext = findCurrentUnmaskedContext(fiber);
return isContextProvider(fiber)
? processChildContext(fiber, parentContext)
: parentContext;
}
function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,

View File

@ -72,7 +72,6 @@ import {
startCommitLifeCyclesTimer,
stopCommitLifeCyclesTimer,
} from './ReactDebugFiberPerf';
import {reset} from './ReactFiberStack';
import {createWorkInProgress} from './ReactFiber';
import {onCommitRoot} from './ReactFiberDevToolsHook';
import {
@ -84,18 +83,14 @@ import {
computeExpirationBucket,
} from './ReactFiberExpirationTime';
import {AsyncMode} from './ReactTypeOfMode';
import {
resetContext as resetLegacyContext,
popContextProvider as popLegacyContextProvider,
popTopLevelContextObject as popTopLevelLegacyContextObject,
} from './ReactFiberContext';
import {popProvider} from './ReactFiberNewContext';
import {resetProviderStack} from './ReactFiberNewContext';
import ReactFiberLegacyContext from './ReactFiberContext';
import ReactFiberNewContext from './ReactFiberNewContext';
import {
getUpdateExpirationTime,
insertUpdateIntoFiber,
} from './ReactFiberUpdateQueue';
import {createCapturedValue} from './ReactCapturedValue';
import ReactFiberStack from './ReactFiberStack';
const {
invokeGuardedCallback,
@ -161,15 +156,24 @@ if (__DEV__) {
export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
) {
const hostContext = ReactFiberHostContext(config);
const stack = ReactFiberStack();
const hostContext = ReactFiberHostContext(config, stack);
const legacyContext = ReactFiberLegacyContext(stack);
const newContext = ReactFiberNewContext(stack);
const {popHostContext, popHostContainer} = hostContext;
const {
popTopLevelContextObject: popTopLevelLegacyContextObject,
popContextProvider: popLegacyContextProvider,
} = legacyContext;
const {popProvider} = newContext;
const hydrationContext: HydrationContext<C, CX> = ReactFiberHydrationContext(
config,
);
const {resetHostContainer} = hostContext;
const {beginWork} = ReactFiberBeginWork(
config,
hostContext,
legacyContext,
newContext,
hydrationContext,
scheduleWork,
computeExpirationForFiber,
@ -177,10 +181,18 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
const {completeWork} = ReactFiberCompleteWork(
config,
hostContext,
legacyContext,
newContext,
hydrationContext,
);
const {throwException, unwindWork} = ReactFiberUnwindWork(
const {
throwException,
unwindWork,
unwindInterruptedWork,
} = ReactFiberUnwindWork(
hostContext,
legacyContext,
newContext,
scheduleWork,
isAlreadyFailedLegacyErrorBoundary,
);
@ -278,18 +290,18 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
};
}
function resetContextStack() {
// Reset the stack
reset();
// Reset the cursors
resetLegacyContext();
resetHostContainer();
// TODO: Unify new context implementation with other stacks
resetProviderStack();
function resetStack() {
if (nextUnitOfWork !== null) {
let interruptedWork = nextUnitOfWork.return;
while (interruptedWork !== null) {
unwindInterruptedWork(interruptedWork);
interruptedWork = interruptedWork.return;
}
}
if (__DEV__) {
ReactStrictModeWarnings.discardPendingWarnings();
stack.checkThatStackIsEmpty();
}
nextRoot = null;
@ -830,7 +842,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
nextUnitOfWork === null
) {
// Reset the stack and start working from the root.
resetContextStack();
resetStack();
nextRoot = root;
nextRenderExpirationTime = expirationTime;
nextUnitOfWork = createWorkInProgress(
@ -883,6 +895,9 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
// Yield back to main thread.
if (didFatal) {
// There was a fatal error.
if (__DEV__) {
stack.resetStackAfterFatalErrorInDev();
}
return null;
} else if (nextUnitOfWork === null) {
// We reached the root.
@ -1091,7 +1106,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
) {
// This is an interruption. (Used for performance tracking.)
interruptedBy = fiber;
resetContextStack();
resetStack();
}
if (nextRoot !== root || !isWorking) {
requestWork(root, expirationTime);
@ -1679,5 +1694,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
interactiveUpdates,
flushInteractiveUpdates,
computeUniqueAsyncExpiration,
legacyContext,
};
}

View File

@ -15,65 +15,54 @@ export type StackCursor<T> = {
current: T,
};
const valueStack: Array<any> = [];
export type Stack = {
createCursor<T>(defaultValue: T): StackCursor<T>,
isEmpty(): boolean,
push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void,
pop<T>(cursor: StackCursor<T>, fiber: Fiber): void,
let fiberStack: Array<Fiber | null>;
// DEV only
checkThatStackIsEmpty(): void,
resetStackAfterFatalErrorInDev(): void,
};
if (__DEV__) {
fiberStack = [];
}
export default function(): Stack {
const valueStack: Array<any> = [];
let index = -1;
let fiberStack: Array<Fiber | null>;
export function createCursor<T>(defaultValue: T): StackCursor<T> {
return {
current: defaultValue,
};
}
if (__DEV__) {
fiberStack = [];
}
export function isEmpty(): boolean {
return index === -1;
}
let index = -1;
function createCursor<T>(defaultValue: T): StackCursor<T> {
return {
current: defaultValue,
};
}
function isEmpty(): boolean {
return index === -1;
}
function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
if (index < 0) {
if (__DEV__) {
warning(false, 'Unexpected pop.');
}
return;
}
export function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
if (index < 0) {
if (__DEV__) {
warning(false, 'Unexpected pop.');
if (fiber !== fiberStack[index]) {
warning(false, 'Unexpected Fiber popped.');
}
}
return;
}
if (__DEV__) {
if (fiber !== fiberStack[index]) {
warning(false, 'Unexpected Fiber popped.');
}
}
cursor.current = valueStack[index];
cursor.current = valueStack[index];
valueStack[index] = null;
if (__DEV__) {
fiberStack[index] = null;
}
index--;
}
export function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
index++;
valueStack[index] = cursor.current;
if (__DEV__) {
fiberStack[index] = fiber;
}
cursor.current = value;
}
export function reset(): void {
while (index > -1) {
valueStack[index] = null;
if (__DEV__) {
@ -82,4 +71,44 @@ export function reset(): void {
index--;
}
function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
index++;
valueStack[index] = cursor.current;
if (__DEV__) {
fiberStack[index] = fiber;
}
cursor.current = value;
}
function checkThatStackIsEmpty() {
if (__DEV__) {
if (index !== -1) {
warning(
false,
'Expected an empty stack. Something was not reset properly.',
);
}
}
}
function resetStackAfterFatalErrorInDev() {
if (__DEV__) {
index = -1;
valueStack.length = 0;
fiberStack.length = 0;
}
}
return {
createCursor,
isEmpty,
pop,
push,
checkThatStackIsEmpty,
resetStackAfterFatalErrorInDev,
};
}

View File

@ -4,8 +4,16 @@
* 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 {ExpirationTime} from './ReactFiberExpirationTime';
import type {HostContext} from './ReactFiberHostContext';
import type {LegacyContext} from './ReactFiberContext';
import type {NewContext} from './ReactFiberNewContext';
import type {UpdateQueue} from './ReactFiberUpdateQueue';
import {createCapturedValue} from './ReactCapturedValue';
import {ensureUpdateQueues} from './ReactFiberUpdateQueue';
@ -25,14 +33,10 @@ import {
import {enableGetDerivedStateFromCatch} from 'shared/ReactFeatureFlags';
import {
popContextProvider as popLegacyContextProvider,
popTopLevelContextObject as popTopLevelLegacyContextObject,
} from './ReactFiberContext';
import {popProvider} from './ReactFiberNewContext';
export default function(
export default function<C, CX>(
hostContext: HostContext<C, CX>,
legacyContext: LegacyContext,
newContext: NewContext,
scheduleWork: (
fiber: Fiber,
startTime: ExpirationTime,
@ -41,6 +45,11 @@ export default function(
isAlreadyFailedLegacyErrorBoundary: (instance: mixed) => boolean,
) {
const {popHostContainer, popHostContext} = hostContext;
const {
popContextProvider: popLegacyContextProvider,
popTopLevelContextObject: popTopLevelLegacyContextObject,
} = legacyContext;
const {popProvider} = newContext;
function throwException(
returnFiber: Fiber,
@ -61,7 +70,9 @@ export default function(
// Uncaught error
const errorInfo = value;
ensureUpdateQueues(workInProgress);
const updateQueue: UpdateQueue = (workInProgress.updateQueue: any);
const updateQueue: UpdateQueue<
any,
> = (workInProgress.updateQueue: any);
updateQueue.capturedValues = [errorInfo];
workInProgress.effectTag |= ShouldCapture;
return;
@ -79,7 +90,9 @@ export default function(
!isAlreadyFailedLegacyErrorBoundary(instance)))
) {
ensureUpdateQueues(workInProgress);
const updateQueue: UpdateQueue = (workInProgress.updateQueue: any);
const updateQueue: UpdateQueue<
any,
> = (workInProgress.updateQueue: any);
const capturedValues = updateQueue.capturedValues;
if (capturedValues === null) {
updateQueue.capturedValues = [value];
@ -97,7 +110,7 @@ export default function(
} while (workInProgress !== null);
}
function unwindWork(workInProgress) {
function unwindWork(workInProgress: Fiber) {
switch (workInProgress.tag) {
case ClassComponent: {
popLegacyContextProvider(workInProgress);
@ -132,8 +145,36 @@ export default function(
return null;
}
}
function unwindInterruptedWork(interruptedWork: Fiber) {
switch (interruptedWork.tag) {
case ClassComponent: {
popLegacyContextProvider(interruptedWork);
break;
}
case HostRoot: {
popHostContainer(interruptedWork);
popTopLevelLegacyContextObject(interruptedWork);
break;
}
case HostComponent: {
popHostContext(interruptedWork);
break;
}
case HostPortal:
popHostContainer(interruptedWork);
break;
case ContextProvider:
popProvider(interruptedWork);
break;
default:
break;
}
}
return {
throwException,
unwindWork,
unwindInterruptedWork,
};
}

View File

@ -542,6 +542,13 @@ ${formatActions(actions)}
['c', step(2)],
['b', interrupt()],
);
simulateMultipleRoots(
['c', toggle(0)],
['c', step(1)],
['b', flush(7)],
['c', toggle(0)],
);
});
it('generative tests', () => {