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', () => {