@samitouri / QOS-React-2 / commits / 208b490ed9

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

Andrew Clark committed Mar 15, 2018 at 19:27 UTC 208b490ed907346ae3e37159535299899f74312d
11 files changed +547 -412
packages/react-reconciler/src/ReactFiberBeginWork.js
+16 -9
@@ -11,6 +11,8 @@ import type {HostConfig} from 'react-reconciler';
11 import type {ReactProviderType, ReactContext} from 'shared/ReactTypes';
12 import type {Fiber} from 'react-reconciler/src/ReactFiber';
13 import type {HostContext} from './ReactFiberHostContext';
14 +import type {LegacyContext} from './ReactFiberContext';
15 +import type {NewContext} from './ReactFiberNewContext';
16 import type {HydrationContext} from './ReactFiberHydrationContext';
17 import type {FiberRoot} from './ReactFiberRoot';
18 import type {ExpirationTime} from './ReactFiberExpirationTime';
@@ -57,15 +59,6 @@ import {
59 cloneChildFibers,
60 } from './ReactChildFiber';
61 import {processUpdateQueue} from './ReactFiberUpdateQueue';
60 -import {
61 - getMaskedContext,
62 - getUnmaskedContext,
63 - hasContextChanged as hasLegacyContextChanged,
64 - pushContextProvider as pushLegacyContextProvider,
65 - pushTopLevelContextObject,
66 - invalidateContextProvider,
67 -} from './ReactFiberContext';
68 -import {pushProvider} from './ReactFiberNewContext';
62 import {NoWork, Never} from './ReactFiberExpirationTime';
63 import {AsyncMode, StrictMode} from './ReactTypeOfMode';
64 import MAX_SIGNED_31_BIT_INT from './maxSigned31BitInt';
@@ -83,6 +76,8 @@ if (__DEV__) {
76 export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
77 config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
78 hostContext: HostContext<C, CX>,
79 + legacyContext: LegacyContext,
80 + newContext: NewContext,
81 hydrationContext: HydrationContext<C, CX>,
82 scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
83 computeExpirationForFiber: (fiber: Fiber) => ExpirationTime,
@@ -91,6 +86,17 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
86
87 const {pushHostContext, pushHostContainer} = hostContext;
88
89 + const {pushProvider} = newContext;
90 +
91 + const {
92 + getMaskedContext,
93 + getUnmaskedContext,
94 + hasContextChanged: hasLegacyContextChanged,
95 + pushContextProvider: pushLegacyContextProvider,
96 + pushTopLevelContextObject,
97 + invalidateContextProvider,
98 + } = legacyContext;
99 +
100 const {
101 enterHydrationState,
102 resetHydrationState,
@@ -105,6 +111,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
111 resumeMountClassInstance,
112 updateClassInstance,
113 } = ReactFiberClassComponent(
114 + legacyContext,
115 scheduleWork,
116 computeExpirationForFiber,
117 memoizeProps,
packages/react-reconciler/src/ReactFiberClassComponent.js
+10 -7
@@ -9,6 +9,7 @@
9
10 import type {Fiber} from './ReactFiber';
11 import type {ExpirationTime} from './ReactFiberExpirationTime';
12 +import type {LegacyContext} from './ReactFiberContext';
13 import type {CapturedValue} from './ReactCapturedValue';
14
15 import {Update} from 'shared/ReactTypeOfSideEffect';
@@ -29,17 +30,10 @@ import warning from 'fbjs/lib/warning';
30
31 import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
32 import {StrictMode} from './ReactTypeOfMode';
32 -import {
33 - cacheContext,
34 - getMaskedContext,
35 - getUnmaskedContext,
36 - isContextConsumer,
37 -} from './ReactFiberContext';
33 import {
34 insertUpdateIntoFiber,
35 processUpdateQueue,
36 } from './ReactFiberUpdateQueue';
42 -import {hasContextChanged} from './ReactFiberContext';
37
38 const fakeInternalInstance = {};
39 const isArray = Array.isArray;
@@ -110,11 +104,20 @@ function callGetDerivedStateFromCatch(ctor: any, capturedValues: Array<mixed>) {
104 }
105
106 export default function(
107 + legacyContext: LegacyContext,
108 scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
109 computeExpirationForFiber: (fiber: Fiber) => ExpirationTime,
110 memoizeProps: (workInProgress: Fiber, props: any) => void,
111 memoizeState: (workInProgress: Fiber, state: any) => void,
112 ) {
113 + const {
114 + cacheContext,
115 + getMaskedContext,
116 + getUnmaskedContext,
117 + isContextConsumer,
118 + hasContextChanged,
119 + } = legacyContext;
120 +
121 // Class component state updater
122 const updater = {
123 isMounted,
packages/react-reconciler/src/ReactFiberCompleteWork.js
+11 -5
@@ -11,6 +11,8 @@ import type {HostConfig} from 'react-reconciler';
11 import type {Fiber} from './ReactFiber';
12 import type {ExpirationTime} from './ReactFiberExpirationTime';
13 import type {HostContext} from './ReactFiberHostContext';
14 +import type {LegacyContext} from './ReactFiberContext';
15 +import type {NewContext} from './ReactFiberNewContext';
16 import type {HydrationContext} from './ReactFiberHydrationContext';
17 import type {FiberRoot} from './ReactFiberRoot';
18
@@ -46,15 +48,12 @@ import {
48 import invariant from 'fbjs/lib/invariant';
49
50 import {reconcileChildFibers} from './ReactChildFiber';
49 -import {
50 - popContextProvider as popLegacyContextProvider,
51 - popTopLevelContextObject as popTopLevelLegacyContextObject,
52 -} from './ReactFiberContext';
53 -import {popProvider} from './ReactFiberNewContext';
51
52 export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
53 config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
54 hostContext: HostContext<C, CX>,
55 + legacyContext: LegacyContext,
56 + newContext: NewContext,
57 hydrationContext: HydrationContext<C, CX>,
58 ) {
59 const {
@@ -74,6 +73,13 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
73 popHostContainer,
74 } = hostContext;
75
76 + const {
77 + popContextProvider: popLegacyContextProvider,
78 + popTopLevelContextObject: popTopLevelLegacyContextObject,
79 + } = legacyContext;
80 +
81 + const {popProvider} = newContext;
82 +
83 const {
84 prepareToHydrateHostInstance,
85 prepareToHydrateHostTextInstance,
packages/react-reconciler/src/ReactFiberContext.js
+270 -236
@@ -8,7 +8,7 @@
8 */
9
10 import type {Fiber} from './ReactFiber';
11 -import type {StackCursor} from './ReactFiberStack';
11 +import type {StackCursor, Stack} from './ReactFiberStack';
12
13 import {isFiberMounted} from 'react-reconciler/reflection';
14 import {ClassComponent, HostRoot} from 'shared/ReactTypeOfWork';
@@ -18,7 +18,6 @@ import invariant from 'fbjs/lib/invariant';
18 import warning from 'fbjs/lib/warning';
19 import checkPropTypes from 'prop-types/checkPropTypes';
20
21 -import {createCursor, pop, push} from './ReactFiberStack';
21 import ReactDebugCurrentFiber from './ReactDebugCurrentFiber';
22 import {startPhaseTimer, stopPhaseTimer} from './ReactDebugFiberPerf';
23
@@ -28,273 +27,308 @@ if (__DEV__) {
27 warnedAboutMissingGetChildContext = {};
28 }
29
31 -// A cursor to the current merged context object on the stack.
32 -let contextStackCursor: StackCursor<Object> = createCursor(emptyObject);
33 -// A cursor to a boolean indicating whether the context has changed.
34 -let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
35 -// Keep track of the previous context object that was on the stack.
36 -// We use this to get access to the parent context after we have already
37 -// pushed the next context provider, and now need to merge their contexts.
38 -let previousContext: Object = emptyObject;
39 -
40 -export function getUnmaskedContext(workInProgress: Fiber): Object {
41 - const hasOwnContext = isContextProvider(workInProgress);
42 - if (hasOwnContext) {
43 - // If the fiber is a context provider itself, when we read its context
44 - // we have already pushed its own child context on the stack. A context
45 - // provider should not "see" its own child context. Therefore we read the
46 - // previous (parent) context instead for a context provider.
47 - return previousContext;
48 - }
49 - return contextStackCursor.current;
50 -}
51 -
52 -export function cacheContext(
53 - workInProgress: Fiber,
54 - unmaskedContext: Object,
55 - maskedContext: Object,
56 -) {
57 - const instance = workInProgress.stateNode;
58 - instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
59 - instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
60 -}
61 -
62 -export function getMaskedContext(
63 - workInProgress: Fiber,
64 - unmaskedContext: Object,
65 -) {
66 - const type = workInProgress.type;
67 - const contextTypes = type.contextTypes;
68 - if (!contextTypes) {
69 - return emptyObject;
30 +export type LegacyContext = {
31 + getUnmaskedContext(workInProgress: Fiber): Object,
32 + cacheContext(
33 + workInProgress: Fiber,
34 + unmaskedContext: Object,
35 + maskedContext: Object,
36 + ): void,
37 + getMaskedContext(workInProgress: Fiber, unmaskedContext: Object): Object,
38 + hasContextChanged(): boolean,
39 + isContextConsumer(fiber: Fiber): boolean,
40 + isContextProvider(fiber: Fiber): boolean,
41 + popContextProvider(fiber: Fiber): void,
42 + popTopLevelContextObject(fiber: Fiber): void,
43 + pushTopLevelContextObject(
44 + fiber: Fiber,
45 + context: Object,
46 + didChange: boolean,
47 + ): void,
48 + processChildContext(fiber: Fiber, parentContext: Object): Object,
49 + pushContextProvider(workInProgress: Fiber): boolean,
50 + invalidateContextProvider(workInProgress: Fiber, didChange: boolean): void,
51 + findCurrentUnmaskedContext(fiber: Fiber): Object,
52 +};
53 +
54 +export default function(stack: Stack): LegacyContext {
55 + const {createCursor, push, pop} = stack;
56 +
57 + // A cursor to the current merged context object on the stack.
58 + let contextStackCursor: StackCursor<Object> = createCursor(emptyObject);
59 + // A cursor to a boolean indicating whether the context has changed.
60 + let didPerformWorkStackCursor: StackCursor<boolean> = createCursor(false);
61 + // Keep track of the previous context object that was on the stack.
62 + // We use this to get access to the parent context after we have already
63 + // pushed the next context provider, and now need to merge their contexts.
64 + let previousContext: Object = emptyObject;
65 +
66 + function getUnmaskedContext(workInProgress: Fiber): Object {
67 + const hasOwnContext = isContextProvider(workInProgress);
68 + if (hasOwnContext) {
69 + // If the fiber is a context provider itself, when we read its context
70 + // we have already pushed its own child context on the stack. A context
71 + // provider should not "see" its own child context. Therefore we read the
72 + // previous (parent) context instead for a context provider.
73 + return previousContext;
74 + }
75 + return contextStackCursor.current;
76 }
77
72 - // Avoid recreating masked context unless unmasked context has changed.
73 - // Failing to do this will result in unnecessary calls to componentWillReceiveProps.
74 - // This may trigger infinite loops if componentWillReceiveProps calls setState.
75 - const instance = workInProgress.stateNode;
76 - if (
77 - instance &&
78 - instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
78 + function cacheContext(
79 + workInProgress: Fiber,
80 + unmaskedContext: Object,
81 + maskedContext: Object,
82 ) {
80 - return instance.__reactInternalMemoizedMaskedChildContext;
81 - }
82 -
83 - const context = {};
84 - for (let key in contextTypes) {
85 - context[key] = unmaskedContext[key];
83 + const instance = workInProgress.stateNode;
84 + instance.__reactInternalMemoizedUnmaskedChildContext = unmaskedContext;
85 + instance.__reactInternalMemoizedMaskedChildContext = maskedContext;
86 }
87
88 - if (__DEV__) {
89 - const name = getComponentName(workInProgress) || 'Unknown';
90 - checkPropTypes(
91 - contextTypes,
92 - context,
93 - 'context',
94 - name,
95 - ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
96 - );
97 - }
98 -
99 - // Cache unmasked context so we can avoid recreating masked context unless necessary.
100 - // Context is created before the class component is instantiated so check for instance.
101 - if (instance) {
102 - cacheContext(workInProgress, unmaskedContext, context);
103 - }
88 + function getMaskedContext(workInProgress: Fiber, unmaskedContext: Object) {
89 + const type = workInProgress.type;
90 + const contextTypes = type.contextTypes;
91 + if (!contextTypes) {
92 + return emptyObject;
93 + }
94
105 - return context;
106 -}
95 + // Avoid recreating masked context unless unmasked context has changed.
96 + // Failing to do this will result in unnecessary calls to componentWillReceiveProps.
97 + // This may trigger infinite loops if componentWillReceiveProps calls setState.
98 + const instance = workInProgress.stateNode;
99 + if (
100 + instance &&
101 + instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext
102 + ) {
103 + return instance.__reactInternalMemoizedMaskedChildContext;
104 + }
105
108 -export function hasContextChanged(): boolean {
109 - return didPerformWorkStackCursor.current;
110 -}
106 + const context = {};
107 + for (let key in contextTypes) {
108 + context[key] = unmaskedContext[key];
109 + }
110
112 -export function isContextConsumer(fiber: Fiber): boolean {
113 - return fiber.tag === ClassComponent && fiber.type.contextTypes != null;
114 -}
111 + if (__DEV__) {
112 + const name = getComponentName(workInProgress) || 'Unknown';
113 + checkPropTypes(
114 + contextTypes,
115 + context,
116 + 'context',
117 + name,
118 + ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
119 + );
120 + }
121
116 -export function isContextProvider(fiber: Fiber): boolean {
117 - return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;
118 -}
122 + // Cache unmasked context so we can avoid recreating masked context unless necessary.
123 + // Context is created before the class component is instantiated so check for instance.
124 + if (instance) {
125 + cacheContext(workInProgress, unmaskedContext, context);
126 + }
127
120 -export function popContextProvider(fiber: Fiber): void {
121 - if (!isContextProvider(fiber)) {
122 - return;
128 + return context;
129 }
130
125 - pop(didPerformWorkStackCursor, fiber);
126 - pop(contextStackCursor, fiber);
127 -}
128 -
129 -export function popTopLevelContextObject(fiber: Fiber) {
130 - pop(didPerformWorkStackCursor, fiber);
131 - pop(contextStackCursor, fiber);
132 -}
131 + function hasContextChanged(): boolean {
132 + return didPerformWorkStackCursor.current;
133 + }
134
134 -export function pushTopLevelContextObject(
135 - fiber: Fiber,
136 - context: Object,
137 - didChange: boolean,
138 -): void {
139 - invariant(
140 - contextStackCursor.cursor == null,
141 - 'Unexpected context found on stack. ' +
142 - 'This error is likely caused by a bug in React. Please file an issue.',
143 - );
144 -
145 - push(contextStackCursor, context, fiber);
146 - push(didPerformWorkStackCursor, didChange, fiber);
147 -}
135 + function isContextConsumer(fiber: Fiber): boolean {
136 + return fiber.tag === ClassComponent && fiber.type.contextTypes != null;
137 + }
138
149 -export function processChildContext(
150 - fiber: Fiber,
151 - parentContext: Object,
152 -): Object {
153 - const instance = fiber.stateNode;
154 - const childContextTypes = fiber.type.childContextTypes;
139 + function isContextProvider(fiber: Fiber): boolean {
140 + return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;
141 + }
142
156 - // TODO (bvaughn) Replace this behavior with an invariant() in the future.
157 - // It has only been added in Fiber to match the (unintentional) behavior in Stack.
158 - if (typeof instance.getChildContext !== 'function') {
159 - if (__DEV__) {
160 - const componentName = getComponentName(fiber) || 'Unknown';
161 -
162 - if (!warnedAboutMissingGetChildContext[componentName]) {
163 - warnedAboutMissingGetChildContext[componentName] = true;
164 - warning(
165 - false,
166 - '%s.childContextTypes is specified but there is no getChildContext() method ' +
167 - 'on the instance. You can either define getChildContext() on %s or remove ' +
168 - 'childContextTypes from it.',
169 - componentName,
170 - componentName,
171 - );
172 - }
143 + function popContextProvider(fiber: Fiber): void {
144 + if (!isContextProvider(fiber)) {
145 + return;
146 }
174 - return parentContext;
175 - }
147
177 - let childContext;
178 - if (__DEV__) {
179 - ReactDebugCurrentFiber.setCurrentPhase('getChildContext');
148 + pop(didPerformWorkStackCursor, fiber);
149 + pop(contextStackCursor, fiber);
150 }
181 - startPhaseTimer(fiber, 'getChildContext');
182 - childContext = instance.getChildContext();
183 - stopPhaseTimer();
184 - if (__DEV__) {
185 - ReactDebugCurrentFiber.setCurrentPhase(null);
151 +
152 + function popTopLevelContextObject(fiber: Fiber) {
153 + pop(didPerformWorkStackCursor, fiber);
154 + pop(contextStackCursor, fiber);
155 }
187 - for (let contextKey in childContext) {
156 +
157 + function pushTopLevelContextObject(
158 + fiber: Fiber,
159 + context: Object,
160 + didChange: boolean,
161 + ): void {
162 invariant(
189 - contextKey in childContextTypes,
190 - '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
191 - getComponentName(fiber) || 'Unknown',
192 - contextKey,
193 - );
194 - }
195 - if (__DEV__) {
196 - const name = getComponentName(fiber) || 'Unknown';
197 - checkPropTypes(
198 - childContextTypes,
199 - childContext,
200 - 'child context',
201 - name,
202 - // In practice, there is one case in which we won't get a stack. It's when
203 - // somebody calls unstable_renderSubtreeIntoContainer() and we process
204 - // context from the parent component instance. The stack will be missing
205 - // because it's outside of the reconciliation, and so the pointer has not
206 - // been set. This is rare and doesn't matter. We'll also remove that API.
207 - ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
163 + contextStackCursor.cursor == null,
164 + 'Unexpected context found on stack. ' +
165 + 'This error is likely caused by a bug in React. Please file an issue.',
166 );
167 +
168 + push(contextStackCursor, context, fiber);
169 + push(didPerformWorkStackCursor, didChange, fiber);
170 }
171
211 - return {...parentContext, ...childContext};
212 -}
172 + function processChildContext(fiber: Fiber, parentContext: Object): Object {
173 + const instance = fiber.stateNode;
174 + const childContextTypes = fiber.type.childContextTypes;
175 +
176 + // TODO (bvaughn) Replace this behavior with an invariant() in the future.
177 + // It has only been added in Fiber to match the (unintentional) behavior in Stack.
178 + if (typeof instance.getChildContext !== 'function') {
179 + if (__DEV__) {
180 + const componentName = getComponentName(fiber) || 'Unknown';
181 +
182 + if (!warnedAboutMissingGetChildContext[componentName]) {
183 + warnedAboutMissingGetChildContext[componentName] = true;
184 + warning(
185 + false,
186 + '%s.childContextTypes is specified but there is no getChildContext() method ' +
187 + 'on the instance. You can either define getChildContext() on %s or remove ' +
188 + 'childContextTypes from it.',
189 + componentName,
190 + componentName,
191 + );
192 + }
193 + }
194 + return parentContext;
195 + }
196 +
197 + let childContext;
198 + if (__DEV__) {
199 + ReactDebugCurrentFiber.setCurrentPhase('getChildContext');
200 + }
201 + startPhaseTimer(fiber, 'getChildContext');
202 + childContext = instance.getChildContext();
203 + stopPhaseTimer();
204 + if (__DEV__) {
205 + ReactDebugCurrentFiber.setCurrentPhase(null);
206 + }
207 + for (let contextKey in childContext) {
208 + invariant(
209 + contextKey in childContextTypes,
210 + '%s.getChildContext(): key "%s" is not defined in childContextTypes.',
211 + getComponentName(fiber) || 'Unknown',
212 + contextKey,
213 + );
214 + }
215 + if (__DEV__) {
216 + const name = getComponentName(fiber) || 'Unknown';
217 + checkPropTypes(
218 + childContextTypes,
219 + childContext,
220 + 'child context',
221 + name,
222 + // In practice, there is one case in which we won't get a stack. It's when
223 + // somebody calls unstable_renderSubtreeIntoContainer() and we process
224 + // context from the parent component instance. The stack will be missing
225 + // because it's outside of the reconciliation, and so the pointer has not
226 + // been set. This is rare and doesn't matter. We'll also remove that API.
227 + ReactDebugCurrentFiber.getCurrentFiberStackAddendum,
228 + );
229 + }
230
214 -export function pushContextProvider(workInProgress: Fiber): boolean {
215 - if (!isContextProvider(workInProgress)) {
216 - return false;
231 + return {...parentContext, ...childContext};
232 }
233
219 - const instance = workInProgress.stateNode;
220 - // We push the context as early as possible to ensure stack integrity.
221 - // If the instance does not exist yet, we will push null at first,
222 - // and replace it on the stack later when invalidating the context.
223 - const memoizedMergedChildContext =
224 - (instance && instance.__reactInternalMemoizedMergedChildContext) ||
225 - emptyObject;
226 -
227 - // Remember the parent context so we can merge with it later.
228 - // Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
229 - previousContext = contextStackCursor.current;
230 - push(contextStackCursor, memoizedMergedChildContext, workInProgress);
231 - push(
232 - didPerformWorkStackCursor,
233 - didPerformWorkStackCursor.current,
234 - workInProgress,
235 - );
236 -
237 - return true;
238 -}
234 + function pushContextProvider(workInProgress: Fiber): boolean {
235 + if (!isContextProvider(workInProgress)) {
236 + return false;
237 + }
238 +
239 + const instance = workInProgress.stateNode;
240 + // We push the context as early as possible to ensure stack integrity.
241 + // If the instance does not exist yet, we will push null at first,
242 + // and replace it on the stack later when invalidating the context.
243 + const memoizedMergedChildContext =
244 + (instance && instance.__reactInternalMemoizedMergedChildContext) ||
245 + emptyObject;
246 +
247 + // Remember the parent context so we can merge with it later.
248 + // Inherit the parent's did-perform-work value to avoid inadvertently blocking updates.
249 + previousContext = contextStackCursor.current;
250 + push(contextStackCursor, memoizedMergedChildContext, workInProgress);
251 + push(
252 + didPerformWorkStackCursor,
253 + didPerformWorkStackCursor.current,
254 + workInProgress,
255 + );
256
240 -export function invalidateContextProvider(
241 - workInProgress: Fiber,
242 - didChange: boolean,
243 -): void {
244 - const instance = workInProgress.stateNode;
245 - invariant(
246 - instance,
247 - 'Expected to have an instance by this point. ' +
248 - 'This error is likely caused by a bug in React. Please file an issue.',
249 - );
250 -
251 - if (didChange) {
252 - // Merge parent and own context.
253 - // Skip this if we're not updating due to sCU.
254 - // This avoids unnecessarily recomputing memoized values.
255 - const mergedContext = processChildContext(workInProgress, previousContext);
256 - instance.__reactInternalMemoizedMergedChildContext = mergedContext;
257 -
258 - // Replace the old (or empty) context with the new one.
259 - // It is important to unwind the context in the reverse order.
260 - pop(didPerformWorkStackCursor, workInProgress);
261 - pop(contextStackCursor, workInProgress);
262 - // Now push the new context and mark that it has changed.
263 - push(contextStackCursor, mergedContext, workInProgress);
264 - push(didPerformWorkStackCursor, didChange, workInProgress);
265 - } else {
266 - pop(didPerformWorkStackCursor, workInProgress);
267 - push(didPerformWorkStackCursor, didChange, workInProgress);
257 + return true;
258 }
269 -}
259
271 -export function resetContext(): void {
272 - previousContext = emptyObject;
273 - contextStackCursor.current = emptyObject;
274 - didPerformWorkStackCursor.current = false;
275 -}
260 + function invalidateContextProvider(
261 + workInProgress: Fiber,
262 + didChange: boolean,
263 + ): void {
264 + const instance = workInProgress.stateNode;
265 + invariant(
266 + instance,
267 + 'Expected to have an instance by this point. ' +
268 + 'This error is likely caused by a bug in React. Please file an issue.',
269 + );
270
277 -export function findCurrentUnmaskedContext(fiber: Fiber): Object {
278 - // Currently this is only used with renderSubtreeIntoContainer; not sure if it
279 - // makes sense elsewhere
280 - invariant(
281 - isFiberMounted(fiber) && fiber.tag === ClassComponent,
282 - 'Expected subtree parent to be a mounted class component. ' +
283 - 'This error is likely caused by a bug in React. Please file an issue.',
284 - );
285 -
286 - let node: Fiber = fiber;
287 - while (node.tag !== HostRoot) {
288 - if (isContextProvider(node)) {
289 - return node.stateNode.__reactInternalMemoizedMergedChildContext;
271 + if (didChange) {
272 + // Merge parent and own context.
273 + // Skip this if we're not updating due to sCU.
274 + // This avoids unnecessarily recomputing memoized values.
275 + const mergedContext = processChildContext(
276 + workInProgress,
277 + previousContext,
278 + );
279 + instance.__reactInternalMemoizedMergedChildContext = mergedContext;
280 +
281 + // Replace the old (or empty) context with the new one.
282 + // It is important to unwind the context in the reverse order.
283 + pop(didPerformWorkStackCursor, workInProgress);
284 + pop(contextStackCursor, workInProgress);
285 + // Now push the new context and mark that it has changed.
286 + push(contextStackCursor, mergedContext, workInProgress);
287 + push(didPerformWorkStackCursor, didChange, workInProgress);
288 + } else {
289 + pop(didPerformWorkStackCursor, workInProgress);
290 + push(didPerformWorkStackCursor, didChange, workInProgress);
291 }
291 - const parent = node.return;
292 + }
293 +
294 + function findCurrentUnmaskedContext(fiber: Fiber): Object {
295 + // Currently this is only used with renderSubtreeIntoContainer; not sure if it
296 + // makes sense elsewhere
297 invariant(
293 - parent,
294 - 'Found unexpected detached subtree parent. ' +
298 + isFiberMounted(fiber) && fiber.tag === ClassComponent,
299 + 'Expected subtree parent to be a mounted class component. ' +
300 'This error is likely caused by a bug in React. Please file an issue.',
301 );
297 - node = parent;
302 +
303 + let node: Fiber = fiber;
304 + while (node.tag !== HostRoot) {
305 + if (isContextProvider(node)) {
306 + return node.stateNode.__reactInternalMemoizedMergedChildContext;
307 + }
308 + const parent = node.return;
309 + invariant(
310 + parent,
311 + 'Found unexpected detached subtree parent. ' +
312 + 'This error is likely caused by a bug in React. Please file an issue.',
313 + );
314 + node = parent;
315 + }
316 + return node.stateNode.context;
317 }
299 - return node.stateNode.context;
318 +
319 + return {
320 + getUnmaskedContext,
321 + cacheContext,
322 + getMaskedContext,
323 + hasContextChanged,
324 + isContextConsumer,
325 + isContextProvider,
326 + popContextProvider,
327 + popTopLevelContextObject,
328 + pushTopLevelContextObject,
329 + processChildContext,
330 + pushContextProvider,
331 + invalidateContextProvider,
332 + findCurrentUnmaskedContext,
333 + };
334 }
packages/react-reconciler/src/ReactFiberHostContext.js
+3 -10
@@ -9,12 +9,10 @@
9
10 import type {HostConfig} from 'react-reconciler';
11 import type {Fiber} from './ReactFiber';
12 -import type {StackCursor} from './ReactFiberStack';
12 +import type {StackCursor, Stack} from './ReactFiberStack';
13
14 import invariant from 'fbjs/lib/invariant';
15
16 -import {createCursor, pop, push} from './ReactFiberStack';
17 -
16 declare class NoContextT {}
17 const NO_CONTEXT: NoContextT = ({}: any);
18
@@ -25,13 +23,14 @@ export type HostContext<C, CX> = {
23 popHostContext(fiber: Fiber): void,
24 pushHostContainer(fiber: Fiber, container: C): void,
25 pushHostContext(fiber: Fiber): void,
28 - resetHostContainer(): void,
26 };
27
28 export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
29 config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
30 + stack: Stack,
31 ): HostContext<C, CX> {
32 const {getChildHostContext, getRootHostContext} = config;
33 + const {createCursor, push, pop} = stack;
34
35 let contextStackCursor: StackCursor<CX | NoContextT> = createCursor(
36 NO_CONTEXT,
@@ -108,11 +107,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
107 pop(contextFiberStackCursor, fiber);
108 }
109
111 - function resetHostContainer() {
112 - contextStackCursor.current = NO_CONTEXT;
113 - rootInstanceStackCursor.current = NO_CONTEXT;
114 - }
115 -
110 return {
111 getHostContext,
112 getRootHostContainer,
@@ -120,6 +114,5 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
114 popHostContext,
115 pushHostContainer,
116 pushHostContext,
123 - resetHostContainer,
117 };
118 }
packages/react-reconciler/src/ReactFiberNewContext.js
+47 -50
@@ -9,67 +9,64 @@
9
10 import type {Fiber} from './ReactFiber';
11 import type {ReactContext} from 'shared/ReactTypes';
12 +import type {StackCursor, Stack} from './ReactFiberStack';
13
14 import warning from 'fbjs/lib/warning';
15
15 -let changedBitsStack: Array<any> = [];
16 -let currentValueStack: Array<any> = [];
17 -let stack: Array<Fiber> = [];
18 -let index = -1;
16 +export type NewContext = {
17 + pushProvider(providerFiber: Fiber): void,
18 + popProvider(providerFiber: Fiber): void,
19 +};
20
20 -let rendererSigil;
21 -if (__DEV__) {
22 - // Use this to detect multiple renderers using the same context
23 - rendererSigil = {};
24 -}
25 -
26 -export function pushProvider(providerFiber: Fiber): void {
27 - const context: ReactContext<any> = providerFiber.type.context;
28 - index += 1;
29 - changedBitsStack[index] = context._changedBits;
30 - currentValueStack[index] = context._currentValue;
31 - stack[index] = providerFiber;
32 - context._currentValue = providerFiber.pendingProps.value;
33 - context._changedBits = providerFiber.stateNode;
21 +export default function(stack: Stack) {
22 + const {createCursor, push, pop} = stack;
23
35 - if (__DEV__) {
36 - warning(
37 - context._currentRenderer === null ||
38 - context._currentRenderer === rendererSigil,
39 - 'Detected multiple renderers concurrently rendering the ' +
40 - 'same context provider. This is currently unsupported.',
41 - );
42 - context._currentRenderer = rendererSigil;
43 - }
44 -}
24 + const providerCursor: StackCursor<Fiber | null> = createCursor(null);
25 + const valueCursor: StackCursor<mixed> = createCursor(null);
26 + const changedBitsCursor: StackCursor<number> = createCursor(0);
27
46 -export function popProvider(providerFiber: Fiber): void {
28 + let rendererSigil;
29 if (__DEV__) {
48 - warning(index > -1 && providerFiber === stack[index], 'Unexpected pop.');
30 + // Use this to detect multiple renderers using the same context
31 + rendererSigil = {};
32 }
50 - const changedBits = changedBitsStack[index];
51 - const currentValue = currentValueStack[index];
52 - changedBitsStack[index] = null;
53 - currentValueStack[index] = null;
54 - stack[index] = null;
55 - index -= 1;
56 - const context: ReactContext<any> = providerFiber.type.context;
57 - context._currentValue = currentValue;
58 - context._changedBits = changedBits;
59 -}
33
61 -export function resetProviderStack(): void {
62 - for (let i = index; i > -1; i--) {
63 - const providerFiber = stack[i];
34 + function pushProvider(providerFiber: Fiber): void {
35 const context: ReactContext<any> = providerFiber.type.context;
65 - context._currentValue = context._defaultValue;
66 - context._changedBits = 0;
67 - changedBitsStack[i] = null;
68 - currentValueStack[i] = null;
69 - stack[i] = null;
36 +
37 + push(changedBitsCursor, context._changedBits, providerFiber);
38 + push(valueCursor, context._currentValue, providerFiber);
39 + push(providerCursor, providerFiber, providerFiber);
40 +
41 + context._currentValue = providerFiber.pendingProps.value;
42 + context._changedBits = providerFiber.stateNode;
43 +
44 if (__DEV__) {
71 - context._currentRenderer = null;
45 + warning(
46 + context._currentRenderer === null ||
47 + context._currentRenderer === rendererSigil,
48 + 'Detected multiple renderers concurrently rendering the ' +
49 + 'same context provider. This is currently unsupported.',
50 + );
51 + context._currentRenderer = rendererSigil;
52 }
53 }
74 - index = -1;
54 +
55 + function popProvider(providerFiber: Fiber): void {
56 + const changedBits = changedBitsCursor.current;
57 + const currentValue = valueCursor.current;
58 +
59 + pop(providerCursor, providerFiber);
60 + pop(valueCursor, providerFiber);
61 + pop(changedBitsCursor, providerFiber);
62 +
63 + const context: ReactContext<any> = providerFiber.type.context;
64 + context._currentValue = currentValue;
65 + context._changedBits = changedBits;
66 + }
67 +
68 + return {
69 + pushProvider,
70 + popProvider,
71 + };
72 }
packages/react-reconciler/src/ReactFiberReconciler.js
+21 -19
@@ -22,11 +22,6 @@ import emptyObject from 'fbjs/lib/emptyObject';
22 import getComponentName from 'shared/getComponentName';
23 import warning from 'fbjs/lib/warning';
24
25 -import {
26 - findCurrentUnmaskedContext,
27 - isContextProvider,
28 - processChildContext,
29 -} from './ReactFiberContext';
25 import {createFiberRoot} from './ReactFiberRoot';
26 import * as ReactFiberDevToolsHook from './ReactFiberDevToolsHook';
27 import ReactFiberScheduler from './ReactFiberScheduler';
@@ -274,20 +269,6 @@ export type Reconciler<C, I, TI> = {
269 findHostInstanceWithNoPortals(component: Fiber): I | TI | null,
270 };
271
277 -function getContextForSubtree(
278 - parentComponent: ?React$Component<any, any>,
279 -): Object {
280 - if (!parentComponent) {
281 - return emptyObject;
282 - }
283 -
284 - const fiber = ReactInstanceMap.get(parentComponent);
285 - const parentContext = findCurrentUnmaskedContext(fiber);
286 - return isContextProvider(fiber)
287 - ? processChildContext(fiber, parentContext)
288 - : parentContext;
289 -}
290 -
272 export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
273 config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
274 ): Reconciler<C, I, TI> {
@@ -308,8 +289,29 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
289 syncUpdates,
290 interactiveUpdates,
291 flushInteractiveUpdates,
292 + legacyContext,
293 } = ReactFiberScheduler(config);
294
295 + const {
296 + findCurrentUnmaskedContext,
297 + isContextProvider,
298 + processChildContext,
299 + } = legacyContext;
300 +
301 + function getContextForSubtree(
302 + parentComponent: ?React$Component<any, any>,
303 + ): Object {
304 + if (!parentComponent) {
305 + return emptyObject;
306 + }
307 +
308 + const fiber = ReactInstanceMap.get(parentComponent);
309 + const parentContext = findCurrentUnmaskedContext(fiber);
310 + return isContextProvider(fiber)
311 + ? processChildContext(fiber, parentContext)
312 + : parentContext;
313 + }
314 +
315 function scheduleRootUpdate(
316 current: Fiber,
317 element: ReactNodeList,
packages/react-reconciler/src/ReactFiberScheduler.js
+38 -22
@@ -72,7 +72,6 @@ import {
72 startCommitLifeCyclesTimer,
73 stopCommitLifeCyclesTimer,
74 } from './ReactDebugFiberPerf';
75 -import {reset} from './ReactFiberStack';
75 import {createWorkInProgress} from './ReactFiber';
76 import {onCommitRoot} from './ReactFiberDevToolsHook';
77 import {
@@ -84,18 +83,14 @@ import {
83 computeExpirationBucket,
84 } from './ReactFiberExpirationTime';
85 import {AsyncMode} from './ReactTypeOfMode';
87 -import {
88 - resetContext as resetLegacyContext,
89 - popContextProvider as popLegacyContextProvider,
90 - popTopLevelContextObject as popTopLevelLegacyContextObject,
91 -} from './ReactFiberContext';
92 -import {popProvider} from './ReactFiberNewContext';
93 -import {resetProviderStack} from './ReactFiberNewContext';
86 +import ReactFiberLegacyContext from './ReactFiberContext';
87 +import ReactFiberNewContext from './ReactFiberNewContext';
88 import {
89 getUpdateExpirationTime,
90 insertUpdateIntoFiber,
91 } from './ReactFiberUpdateQueue';
92 import {createCapturedValue} from './ReactCapturedValue';
93 +import ReactFiberStack from './ReactFiberStack';
94
95 const {
96 invokeGuardedCallback,
@@ -161,15 +156,24 @@ if (__DEV__) {
156 export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
157 config: HostConfig<T, P, I, TI, HI, PI, C, CC, CX, PL>,
158 ) {
164 - const hostContext = ReactFiberHostContext(config);
159 + const stack = ReactFiberStack();
160 + const hostContext = ReactFiberHostContext(config, stack);
161 + const legacyContext = ReactFiberLegacyContext(stack);
162 + const newContext = ReactFiberNewContext(stack);
163 const {popHostContext, popHostContainer} = hostContext;
164 + const {
165 + popTopLevelContextObject: popTopLevelLegacyContextObject,
166 + popContextProvider: popLegacyContextProvider,
167 + } = legacyContext;
168 + const {popProvider} = newContext;
169 const hydrationContext: HydrationContext<C, CX> = ReactFiberHydrationContext(
170 config,
171 );
169 - const {resetHostContainer} = hostContext;
172 const {beginWork} = ReactFiberBeginWork(
173 config,
174 hostContext,
175 + legacyContext,
176 + newContext,
177 hydrationContext,
178 scheduleWork,
179 computeExpirationForFiber,
@@ -177,10 +181,18 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
181 const {completeWork} = ReactFiberCompleteWork(
182 config,
183 hostContext,
184 + legacyContext,
185 + newContext,
186 hydrationContext,
187 );
182 - const {throwException, unwindWork} = ReactFiberUnwindWork(
188 + const {
189 + throwException,
190 + unwindWork,
191 + unwindInterruptedWork,
192 + } = ReactFiberUnwindWork(
193 hostContext,
194 + legacyContext,
195 + newContext,
196 scheduleWork,
197 isAlreadyFailedLegacyErrorBoundary,
198 );
@@ -278,18 +290,18 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
290 };
291 }
292
281 - function resetContextStack() {
282 - // Reset the stack
283 - reset();
284 - // Reset the cursors
285 - resetLegacyContext();
286 - resetHostContainer();
287 -
288 - // TODO: Unify new context implementation with other stacks
289 - resetProviderStack();
293 + function resetStack() {
294 + if (nextUnitOfWork !== null) {
295 + let interruptedWork = nextUnitOfWork.return;
296 + while (interruptedWork !== null) {
297 + unwindInterruptedWork(interruptedWork);
298 + interruptedWork = interruptedWork.return;
299 + }
300 + }
301
302 if (__DEV__) {
303 ReactStrictModeWarnings.discardPendingWarnings();
304 + stack.checkThatStackIsEmpty();
305 }
306
307 nextRoot = null;
@@ -830,7 +842,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
842 nextUnitOfWork === null
843 ) {
844 // Reset the stack and start working from the root.
833 - resetContextStack();
845 + resetStack();
846 nextRoot = root;
847 nextRenderExpirationTime = expirationTime;
848 nextUnitOfWork = createWorkInProgress(
@@ -883,6 +895,9 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
895 // Yield back to main thread.
896 if (didFatal) {
897 // There was a fatal error.
898 + if (__DEV__) {
899 + stack.resetStackAfterFatalErrorInDev();
900 + }
901 return null;
902 } else if (nextUnitOfWork === null) {
903 // We reached the root.
@@ -1091,7 +1106,7 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
1106 ) {
1107 // This is an interruption. (Used for performance tracking.)
1108 interruptedBy = fiber;
1094 - resetContextStack();
1109 + resetStack();
1110 }
1111 if (nextRoot !== root || !isWorking) {
1112 requestWork(root, expirationTime);
@@ -1679,5 +1694,6 @@ export default function<T, P, I, TI, HI, PI, C, CC, CX, PL>(
1694 interactiveUpdates,
1695 flushInteractiveUpdates,
1696 computeUniqueAsyncExpiration,
1697 + legacyContext,
1698 };
1699 }
packages/react-reconciler/src/ReactFiberStack.js
+73 -44
@@ -15,71 +15,100 @@ export type StackCursor<T> = {
15 current: T,
16 };
17
18 -const valueStack: Array<any> = [];
18 +export type Stack = {
19 + createCursor<T>(defaultValue: T): StackCursor<T>,
20 + isEmpty(): boolean,
21 + push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void,
22 + pop<T>(cursor: StackCursor<T>, fiber: Fiber): void,
23 +
24 + // DEV only
25 + checkThatStackIsEmpty(): void,
26 + resetStackAfterFatalErrorInDev(): void,
27 +};
28
20 -let fiberStack: Array<Fiber | null>;
29 +export default function(): Stack {
30 + const valueStack: Array<any> = [];
31
22 -if (__DEV__) {
23 - fiberStack = [];
24 -}
32 + let fiberStack: Array<Fiber | null>;
33
26 -let index = -1;
34 + if (__DEV__) {
35 + fiberStack = [];
36 + }
37
28 -export function createCursor<T>(defaultValue: T): StackCursor<T> {
29 - return {
30 - current: defaultValue,
31 - };
32 -}
38 + let index = -1;
39
34 -export function isEmpty(): boolean {
35 - return index === -1;
36 -}
40 + function createCursor<T>(defaultValue: T): StackCursor<T> {
41 + return {
42 + current: defaultValue,
43 + };
44 + }
45
38 -export function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
39 - if (index < 0) {
40 - if (__DEV__) {
41 - warning(false, 'Unexpected pop.');
42 - }
43 - return;
46 + function isEmpty(): boolean {
47 + return index === -1;
48 }
49
46 - if (__DEV__) {
47 - if (fiber !== fiberStack[index]) {
48 - warning(false, 'Unexpected Fiber popped.');
50 + function pop<T>(cursor: StackCursor<T>, fiber: Fiber): void {
51 + if (index < 0) {
52 + if (__DEV__) {
53 + warning(false, 'Unexpected pop.');
54 + }
55 + return;
56 }
50 - }
57
52 - cursor.current = valueStack[index];
58 + if (__DEV__) {
59 + if (fiber !== fiberStack[index]) {
60 + warning(false, 'Unexpected Fiber popped.');
61 + }
62 + }
63
54 - valueStack[index] = null;
64 + cursor.current = valueStack[index];
65
56 - if (__DEV__) {
57 - fiberStack[index] = null;
58 - }
66 + valueStack[index] = null;
67
60 - index--;
61 -}
68 + if (__DEV__) {
69 + fiberStack[index] = null;
70 + }
71
63 -export function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
64 - index++;
72 + index--;
73 + }
74
66 - valueStack[index] = cursor.current;
75 + function push<T>(cursor: StackCursor<T>, value: T, fiber: Fiber): void {
76 + index++;
77
68 - if (__DEV__) {
69 - fiberStack[index] = fiber;
70 - }
78 + valueStack[index] = cursor.current;
79
72 - cursor.current = value;
73 -}
80 + if (__DEV__) {
81 + fiberStack[index] = fiber;
82 + }
83
75 -export function reset(): void {
76 - while (index > -1) {
77 - valueStack[index] = null;
84 + cursor.current = value;
85 + }
86
87 + function checkThatStackIsEmpty() {
88 if (__DEV__) {
80 - fiberStack[index] = null;
89 + if (index !== -1) {
90 + warning(
91 + false,
92 + 'Expected an empty stack. Something was not reset properly.',
93 + );
94 + }
95 }
96 + }
97
83 - index--;
98 + function resetStackAfterFatalErrorInDev() {
99 + if (__DEV__) {
100 + index = -1;
101 + valueStack.length = 0;
102 + fiberStack.length = 0;
103 + }
104 }
105 +
106 + return {
107 + createCursor,
108 + isEmpty,
109 + pop,
110 + push,
111 + checkThatStackIsEmpty,
112 + resetStackAfterFatalErrorInDev,
113 + };
114 }
packages/react-reconciler/src/ReactFiberUnwindWork.js
+51 -10
@@ -4,8 +4,16 @@
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 + * @flow
8 */
9
10 +import type {Fiber} from './ReactFiber';
11 +import type {ExpirationTime} from './ReactFiberExpirationTime';
12 +import type {HostContext} from './ReactFiberHostContext';
13 +import type {LegacyContext} from './ReactFiberContext';
14 +import type {NewContext} from './ReactFiberNewContext';
15 +import type {UpdateQueue} from './ReactFiberUpdateQueue';
16 +
17 import {createCapturedValue} from './ReactCapturedValue';
18 import {ensureUpdateQueues} from './ReactFiberUpdateQueue';
19
@@ -25,14 +33,10 @@ import {
33
34 import {enableGetDerivedStateFromCatch} from 'shared/ReactFeatureFlags';
35
28 -import {
29 - popContextProvider as popLegacyContextProvider,
30 - popTopLevelContextObject as popTopLevelLegacyContextObject,
31 -} from './ReactFiberContext';
32 -import {popProvider} from './ReactFiberNewContext';
33 -
34 -export default function(
36 +export default function<C, CX>(
37 hostContext: HostContext<C, CX>,
38 + legacyContext: LegacyContext,
39 + newContext: NewContext,
40 scheduleWork: (
41 fiber: Fiber,
42 startTime: ExpirationTime,
@@ -41,6 +45,11 @@ export default function(
45 isAlreadyFailedLegacyErrorBoundary: (instance: mixed) => boolean,
46 ) {
47 const {popHostContainer, popHostContext} = hostContext;
48 + const {
49 + popContextProvider: popLegacyContextProvider,
50 + popTopLevelContextObject: popTopLevelLegacyContextObject,
51 + } = legacyContext;
52 + const {popProvider} = newContext;
53
54 function throwException(
55 returnFiber: Fiber,
@@ -61,7 +70,9 @@ export default function(
70 // Uncaught error
71 const errorInfo = value;
72 ensureUpdateQueues(workInProgress);
64 - const updateQueue: UpdateQueue = (workInProgress.updateQueue: any);
73 + const updateQueue: UpdateQueue<
74 + any,
75 + > = (workInProgress.updateQueue: any);
76 updateQueue.capturedValues = [errorInfo];
77 workInProgress.effectTag |= ShouldCapture;
78 return;
@@ -79,7 +90,9 @@ export default function(
90 !isAlreadyFailedLegacyErrorBoundary(instance)))
91 ) {
92 ensureUpdateQueues(workInProgress);
82 - const updateQueue: UpdateQueue = (workInProgress.updateQueue: any);
93 + const updateQueue: UpdateQueue<
94 + any,
95 + > = (workInProgress.updateQueue: any);
96 const capturedValues = updateQueue.capturedValues;
97 if (capturedValues === null) {
98 updateQueue.capturedValues = [value];
@@ -97,7 +110,7 @@ export default function(
110 } while (workInProgress !== null);
111 }
112
100 - function unwindWork(workInProgress) {
113 + function unwindWork(workInProgress: Fiber) {
114 switch (workInProgress.tag) {
115 case ClassComponent: {
116 popLegacyContextProvider(workInProgress);
@@ -132,8 +145,36 @@ export default function(
145 return null;
146 }
147 }
148 +
149 + function unwindInterruptedWork(interruptedWork: Fiber) {
150 + switch (interruptedWork.tag) {
151 + case ClassComponent: {
152 + popLegacyContextProvider(interruptedWork);
153 + break;
154 + }
155 + case HostRoot: {
156 + popHostContainer(interruptedWork);
157 + popTopLevelLegacyContextObject(interruptedWork);
158 + break;
159 + }
160 + case HostComponent: {
161 + popHostContext(interruptedWork);
162 + break;
163 + }
164 + case HostPortal:
165 + popHostContainer(interruptedWork);
166 + break;
167 + case ContextProvider:
168 + popProvider(interruptedWork);
169 + break;
170 + default:
171 + break;
172 + }
173 + }
174 +
175 return {
176 throwException,
177 unwindWork,
178 + unwindInterruptedWork,
179 };
180 }
packages/react-reconciler/src/__tests__/ReactIncrementalTriangle-test.internal.js
+7
@@ -542,6 +542,13 @@ ${formatActions(actions)}
542 ['c', step(2)],
543 ['b', interrupt()],
544 );
545 +
546 + simulateMultipleRoots(
547 + ['c', toggle(0)],
548 + ['c', step(1)],
549 + ['b', flush(7)],
550 + ['c', toggle(0)],
551 + );
552 });
553
554 it('generative tests', () => {