diff --git a/packages/react-reconciler/src/ReactFiberBeginWork.js b/packages/react-reconciler/src/ReactFiberBeginWork.js index 95914814bc1e0..ca8c94288c562 100644 --- a/packages/react-reconciler/src/ReactFiberBeginWork.js +++ b/packages/react-reconciler/src/ReactFiberBeginWork.js @@ -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'; @@ -56,15 +58,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'; @@ -82,6 +75,8 @@ if (__DEV__) { export default function( config: HostConfig, hostContext: HostContext, + legacyContext: LegacyContext, + newContext: NewContext, hydrationContext: HydrationContext, scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void, computeExpirationForFiber: (fiber: Fiber) => ExpirationTime, @@ -90,6 +85,17 @@ export default function( const {pushHostContext, pushHostContainer} = hostContext; + const {pushProvider} = newContext; + + const { + getMaskedContext, + getUnmaskedContext, + hasContextChanged: hasLegacyContextChanged, + pushContextProvider: pushLegacyContextProvider, + pushTopLevelContextObject, + invalidateContextProvider, + } = legacyContext; + const { enterHydrationState, resetHydrationState, @@ -104,6 +110,7 @@ export default function( resumeMountClassInstance, updateClassInstance, } = ReactFiberClassComponent( + legacyContext, scheduleWork, computeExpirationForFiber, memoizeProps, diff --git a/packages/react-reconciler/src/ReactFiberClassComponent.js b/packages/react-reconciler/src/ReactFiberClassComponent.js index 6d682cd2329a6..d8c2cd2069527 100644 --- a/packages/react-reconciler/src/ReactFiberClassComponent.js +++ b/packages/react-reconciler/src/ReactFiberClassComponent.js @@ -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) { } 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, diff --git a/packages/react-reconciler/src/ReactFiberCompleteWork.js b/packages/react-reconciler/src/ReactFiberCompleteWork.js index 9e796dd53c152..9f5ec49174ed3 100644 --- a/packages/react-reconciler/src/ReactFiberCompleteWork.js +++ b/packages/react-reconciler/src/ReactFiberCompleteWork.js @@ -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'; @@ -45,15 +47,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( config: HostConfig, hostContext: HostContext, + legacyContext: LegacyContext, + newContext: NewContext, hydrationContext: HydrationContext, ) { const { @@ -73,6 +72,13 @@ export default function( popHostContainer, } = hostContext; + const { + popContextProvider: popLegacyContextProvider, + popTopLevelContextObject: popTopLevelLegacyContextObject, + } = legacyContext; + + const {popProvider} = newContext; + const { prepareToHydrateHostInstance, prepareToHydrateHostTextInstance, diff --git a/packages/react-reconciler/src/ReactFiberContext.js b/packages/react-reconciler/src/ReactFiberContext.js index 7413eeaf88166..5db08417035f4 100644 --- a/packages/react-reconciler/src/ReactFiberContext.js +++ b/packages/react-reconciler/src/ReactFiberContext.js @@ -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 = createCursor(emptyObject); -// A cursor to a boolean indicating whether the context has changed. -let didPerformWorkStackCursor: StackCursor = 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 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 function cacheContext( - workInProgress: Fiber, - unmaskedContext: Object, - maskedContext: Object, -) { - const instance = workInProgress.stateNode; - instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext; - instance.__reactInternalMemoizedMaskedChildContext = maskedContext; -} - -export function getMaskedContext( - workInProgress: Fiber, - unmaskedContext: Object, -) { - const type = workInProgress.type; - const contextTypes = type.contextTypes; - if (!contextTypes) { - return 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 default function(stack: Stack): LegacyContext { + const {createCursor, push, pop} = stack; + + // A cursor to the current merged context object on the stack. + let contextStackCursor: StackCursor = createCursor(emptyObject); + // A cursor to a boolean indicating whether the context has changed. + let didPerformWorkStackCursor: StackCursor = 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; } - // 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 context = {}; - for (let key in contextTypes) { - context[key] = unmaskedContext[key]; + const instance = workInProgress.stateNode; + instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext; + instance.__reactInternalMemoizedMaskedChildContext = maskedContext; } - if (__DEV__) { - const name = getComponentName(workInProgress) || 'Unknown'; - checkPropTypes( - contextTypes, - context, - 'context', - name, - ReactDebugCurrentFiber.getCurrentFiberStackAddendum, - ); - } - - // 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); - } + function getMaskedContext(workInProgress: Fiber, unmaskedContext: Object) { + const type = workInProgress.type; + const contextTypes = type.contextTypes; + if (!contextTypes) { + return emptyObject; + } - return context; -} + // 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; + } -export function hasContextChanged(): boolean { - return didPerformWorkStackCursor.current; -} + const context = {}; + for (let key in contextTypes) { + context[key] = unmaskedContext[key]; + } -export function isContextConsumer(fiber: Fiber): boolean { - return fiber.tag === ClassComponent && fiber.type.contextTypes != null; -} + if (__DEV__) { + const name = getComponentName(workInProgress) || 'Unknown'; + checkPropTypes( + contextTypes, + context, + 'context', + name, + ReactDebugCurrentFiber.getCurrentFiberStackAddendum, + ); + } -export function isContextProvider(fiber: Fiber): boolean { - return fiber.tag === ClassComponent && fiber.type.childContextTypes != 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); + } -export function popContextProvider(fiber: Fiber): void { - if (!isContextProvider(fiber)) { - return; + return context; } - pop(didPerformWorkStackCursor, fiber); - pop(contextStackCursor, fiber); -} - -export function popTopLevelContextObject(fiber: Fiber) { - pop(didPerformWorkStackCursor, fiber); - pop(contextStackCursor, fiber); -} + function hasContextChanged(): boolean { + return didPerformWorkStackCursor.current; + } -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); -} + function isContextConsumer(fiber: Fiber): boolean { + return fiber.tag === ClassComponent && fiber.type.contextTypes != null; + } -export function processChildContext( - fiber: Fiber, - parentContext: Object, -): Object { - const instance = fiber.stateNode; - const childContextTypes = fiber.type.childContextTypes; + function isContextProvider(fiber: Fiber): boolean { + return fiber.tag === ClassComponent && fiber.type.childContextTypes != null; + } - // 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, - ); - } + function popContextProvider(fiber: Fiber): void { + if (!isContextProvider(fiber)) { + return; } - return parentContext; - } - let childContext; - if (__DEV__) { - ReactDebugCurrentFiber.setCurrentPhase('getChildContext'); + pop(didPerformWorkStackCursor, fiber); + pop(contextStackCursor, fiber); } - startPhaseTimer(fiber, 'getChildContext'); - childContext = instance.getChildContext(); - stopPhaseTimer(); - if (__DEV__) { - ReactDebugCurrentFiber.setCurrentPhase(null); + + function popTopLevelContextObject(fiber: Fiber) { + pop(didPerformWorkStackCursor, fiber); + pop(contextStackCursor, fiber); } - for (let contextKey in childContext) { + + function pushTopLevelContextObject( + fiber: Fiber, + context: Object, + didChange: boolean, + ): void { 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, + 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); } - return {...parentContext, ...childContext}; -} + 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, + ); + } -export function pushContextProvider(workInProgress: Fiber): boolean { - if (!isContextProvider(workInProgress)) { - return false; + return {...parentContext, ...childContext}; } - 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 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, + ); -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); + return true; } -} -export function resetContext(): void { - previousContext = emptyObject; - contextStackCursor.current = emptyObject; - didPerformWorkStackCursor.current = false; -} + 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.', + ); -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; + 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); } - const parent = node.return; + } + + function findCurrentUnmaskedContext(fiber: Fiber): Object { + // Currently this is only used with renderSubtreeIntoContainer; not sure if it + // makes sense elsewhere invariant( - parent, - 'Found unexpected detached subtree parent. ' + + 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.', ); - node = parent; + + 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 node.stateNode.context; + + return { + getUnmaskedContext, + cacheContext, + getMaskedContext, + hasContextChanged, + isContextConsumer, + isContextProvider, + popContextProvider, + popTopLevelContextObject, + pushTopLevelContextObject, + processChildContext, + pushContextProvider, + invalidateContextProvider, + findCurrentUnmaskedContext, + }; } diff --git a/packages/react-reconciler/src/ReactFiberHostContext.js b/packages/react-reconciler/src/ReactFiberHostContext.js index 7bb479b821ff2..f58651381a28c 100644 --- a/packages/react-reconciler/src/ReactFiberHostContext.js +++ b/packages/react-reconciler/src/ReactFiberHostContext.js @@ -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 = { popHostContext(fiber: Fiber): void, pushHostContainer(fiber: Fiber, container: C): void, pushHostContext(fiber: Fiber): void, - resetHostContainer(): void, }; export default function( config: HostConfig, + stack: Stack, ): HostContext { const {getChildHostContext, getRootHostContext} = config; + const {createCursor, push, pop} = stack; let contextStackCursor: StackCursor = createCursor( NO_CONTEXT, @@ -108,11 +107,6 @@ export default function( pop(contextFiberStackCursor, fiber); } - function resetHostContainer() { - contextStackCursor.current = NO_CONTEXT; - rootInstanceStackCursor.current = NO_CONTEXT; - } - return { getHostContext, getRootHostContainer, @@ -120,6 +114,5 @@ export default function( popHostContext, pushHostContainer, pushHostContext, - resetHostContainer, }; } diff --git a/packages/react-reconciler/src/ReactFiberNewContext.js b/packages/react-reconciler/src/ReactFiberNewContext.js index 3c8bf67fd01e2..5f64352438ef4 100644 --- a/packages/react-reconciler/src/ReactFiberNewContext.js +++ b/packages/react-reconciler/src/ReactFiberNewContext.js @@ -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 = []; -let currentValueStack: Array = []; -let stack: Array = []; -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 function pushProvider(providerFiber: Fiber): void { - const context: ReactContext = 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; +export default function(stack: Stack) { + const {createCursor, push, pop} = stack; - 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; - } -} + const providerCursor: StackCursor = createCursor(null); + const valueCursor: StackCursor = createCursor(null); + const changedBitsCursor: StackCursor = createCursor(0); -export function popProvider(providerFiber: Fiber): void { + let rendererSigil; if (__DEV__) { - warning(index > -1 && providerFiber === stack[index], 'Unexpected pop.'); + // Use this to detect multiple renderers using the same context + rendererSigil = {}; } - const changedBits = changedBitsStack[index]; - const currentValue = currentValueStack[index]; - changedBitsStack[index] = null; - currentValueStack[index] = null; - stack[index] = null; - index -= 1; - const context: ReactContext = 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 = 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 = providerFiber.type.context; + context._currentValue = currentValue; + context._changedBits = changedBits; + } + + return { + pushProvider, + popProvider, + }; } diff --git a/packages/react-reconciler/src/ReactFiberReconciler.js b/packages/react-reconciler/src/ReactFiberReconciler.js index 949e806779418..bb13271f0603d 100644 --- a/packages/react-reconciler/src/ReactFiberReconciler.js +++ b/packages/react-reconciler/src/ReactFiberReconciler.js @@ -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 = { findHostInstanceWithNoPortals(component: Fiber): I | TI | null, }; -function getContextForSubtree( - parentComponent: ?React$Component, -): Object { - if (!parentComponent) { - return emptyObject; - } - - const fiber = ReactInstanceMap.get(parentComponent); - const parentContext = findCurrentUnmaskedContext(fiber); - return isContextProvider(fiber) - ? processChildContext(fiber, parentContext) - : parentContext; -} - export default function( config: HostConfig, ): Reconciler { @@ -308,8 +289,29 @@ export default function( syncUpdates, interactiveUpdates, flushInteractiveUpdates, + legacyContext, } = ReactFiberScheduler(config); + const { + findCurrentUnmaskedContext, + isContextProvider, + processChildContext, + } = legacyContext; + + function getContextForSubtree( + parentComponent: ?React$Component, + ): 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, diff --git a/packages/react-reconciler/src/ReactFiberScheduler.js b/packages/react-reconciler/src/ReactFiberScheduler.js index 09fb2679e4e79..ea5df6dc84128 100644 --- a/packages/react-reconciler/src/ReactFiberScheduler.js +++ b/packages/react-reconciler/src/ReactFiberScheduler.js @@ -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( config: HostConfig, ) { - 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 = ReactFiberHydrationContext( config, ); - const {resetHostContainer} = hostContext; const {beginWork} = ReactFiberBeginWork( config, hostContext, + legacyContext, + newContext, hydrationContext, scheduleWork, computeExpirationForFiber, @@ -177,10 +181,18 @@ export default function( 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( }; } - 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( 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( // 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( ) { // 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( interactiveUpdates, flushInteractiveUpdates, computeUniqueAsyncExpiration, + legacyContext, }; } diff --git a/packages/react-reconciler/src/ReactFiberStack.js b/packages/react-reconciler/src/ReactFiberStack.js index c15214f267d43..abf2a475b1ae5 100644 --- a/packages/react-reconciler/src/ReactFiberStack.js +++ b/packages/react-reconciler/src/ReactFiberStack.js @@ -15,71 +15,100 @@ export type StackCursor = { current: T, }; -const valueStack: Array = []; +export type Stack = { + createCursor(defaultValue: T): StackCursor, + isEmpty(): boolean, + push(cursor: StackCursor, value: T, fiber: Fiber): void, + pop(cursor: StackCursor, fiber: Fiber): void, + + // DEV only + checkThatStackIsEmpty(): void, + resetStackAfterFatalErrorInDev(): void, +}; -let fiberStack: Array; +export default function(): Stack { + const valueStack: Array = []; -if (__DEV__) { - fiberStack = []; -} + let fiberStack: Array; -let index = -1; + if (__DEV__) { + fiberStack = []; + } -export function createCursor(defaultValue: T): StackCursor { - return { - current: defaultValue, - }; -} + let index = -1; -export function isEmpty(): boolean { - return index === -1; -} + function createCursor(defaultValue: T): StackCursor { + return { + current: defaultValue, + }; + } -export function pop(cursor: StackCursor, fiber: Fiber): void { - if (index < 0) { - if (__DEV__) { - warning(false, 'Unexpected pop.'); - } - return; + function isEmpty(): boolean { + return index === -1; } - if (__DEV__) { - if (fiber !== fiberStack[index]) { - warning(false, 'Unexpected Fiber popped.'); + function pop(cursor: StackCursor, fiber: Fiber): void { + if (index < 0) { + if (__DEV__) { + warning(false, 'Unexpected pop.'); + } + return; } - } - cursor.current = valueStack[index]; + if (__DEV__) { + if (fiber !== fiberStack[index]) { + warning(false, 'Unexpected Fiber popped.'); + } + } - valueStack[index] = null; + cursor.current = valueStack[index]; - if (__DEV__) { - fiberStack[index] = null; - } + valueStack[index] = null; - index--; -} + if (__DEV__) { + fiberStack[index] = null; + } -export function push(cursor: StackCursor, value: T, fiber: Fiber): void { - index++; + index--; + } - valueStack[index] = cursor.current; + function push(cursor: StackCursor, value: T, fiber: Fiber): void { + index++; - if (__DEV__) { - fiberStack[index] = fiber; - } + valueStack[index] = cursor.current; - cursor.current = value; -} + if (__DEV__) { + fiberStack[index] = fiber; + } -export function reset(): void { - while (index > -1) { - valueStack[index] = null; + cursor.current = value; + } + function checkThatStackIsEmpty() { if (__DEV__) { - fiberStack[index] = null; + if (index !== -1) { + warning( + false, + 'Expected an empty stack. Something was not reset properly.', + ); + } } + } - index--; + function resetStackAfterFatalErrorInDev() { + if (__DEV__) { + index = -1; + valueStack.length = 0; + fiberStack.length = 0; + } } + + return { + createCursor, + isEmpty, + pop, + push, + checkThatStackIsEmpty, + resetStackAfterFatalErrorInDev, + }; } diff --git a/packages/react-reconciler/src/ReactFiberUnwindWork.js b/packages/react-reconciler/src/ReactFiberUnwindWork.js index bb3ce84945d2b..6565e4888df1b 100644 --- a/packages/react-reconciler/src/ReactFiberUnwindWork.js +++ b/packages/react-reconciler/src/ReactFiberUnwindWork.js @@ -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( hostContext: HostContext, + 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, }; } diff --git a/packages/react-reconciler/src/__tests__/ReactIncrementalTriangle-test.internal.js b/packages/react-reconciler/src/__tests__/ReactIncrementalTriangle-test.internal.js index 283c7dc4f8f15..628e1c38806b5 100644 --- a/packages/react-reconciler/src/__tests__/ReactIncrementalTriangle-test.internal.js +++ b/packages/react-reconciler/src/__tests__/ReactIncrementalTriangle-test.internal.js @@ -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', () => {