Move context management into scheduler
It is error-prone to push and pop context in begin or complete phases because of all the bailouts. Scheduler has enough knowledge to see if pushing is necessary because it knows when we go inside or outside the tree.
Dan Abramov committed
Nov 14, 2016 at 00:34 UTC
f9e1bc9c97251e5593d102f0e553b8af4ad0865a
5 files changed
+50
-40
scripts/fiber/tests-passing.txt
+2
@@ -819,6 +819,8 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
819
* can nest batchedUpdates
820
* can handle if setState callback throws
821
* merges and masks context
822
+* does not leak own context into context provider
823
+* provides context when reusing work
824
825
src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js
826
* catches render error in a boundary during mounting
src/renderers/shared/fiber/ReactFiberBeginWork.js
+1
-9
@@ -27,8 +27,6 @@ var {
27
var ReactTypeOfWork = require('ReactTypeOfWork');
28
var {
29
getMaskedContext,
30
- pushContextProvider,
31
- resetContext,
30
} = require('ReactFiberContext');
31
var {
32
IndeterminateComponent,
@@ -189,14 +187,11 @@ module.exports = function<T, P, I, TI, C>(
187
} else {
188
shouldUpdate = updateClassInstance(current, workInProgress);
189
}
192
- const instance = workInProgress.stateNode;
193
- if (typeof instance.getChildContext === 'function') {
194
- pushContextProvider(workInProgress);
195
- }
190
if (!shouldUpdate) {
191
return bailoutOnAlreadyFinishedWork(current, workInProgress);
192
}
193
// Rerender
194
+ const instance = workInProgress.stateNode;
195
ReactCurrentOwner.current = workInProgress;
196
const nextChildren = instance.render();
197
reconcileChildren(current, workInProgress, nextChildren);
@@ -367,9 +362,6 @@ module.exports = function<T, P, I, TI, C>(
362
}
363
364
function beginWork(current : ?Fiber, workInProgress : Fiber, priorityLevel : PriorityLevel) : ?Fiber {
370
- if (!workInProgress.return) {
371
- resetContext();
372
- }
365
if (workInProgress.pendingWorkPriority === NoWork ||
366
workInProgress.pendingWorkPriority > priorityLevel) {
367
return bailoutOnLowPriority(current, workInProgress);
src/renderers/shared/fiber/ReactFiberCompleteWork.js
+11
-27
@@ -18,7 +18,6 @@ import type { HostConfig } from 'ReactFiberReconciler';
18
import type { ReifiedYield } from 'ReactReifiedYield';
19
20
var { reconcileChildFibers } = require('ReactChildFiber');
21
-var { popContextProvider } = require('ReactFiberContext');
21
var ReactTypeOfWork = require('ReactTypeOfWork');
22
var ReactTypeOfSideEffect = require('ReactTypeOfSideEffect');
23
var {
@@ -121,11 +120,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
120
121
function completeWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
122
switch (workInProgress.tag) {
124
- case FunctionalComponent: {
123
+ case FunctionalComponent:
124
transferOutput(workInProgress.child, workInProgress);
125
return null;
127
- }
128
- case ClassComponent: {
126
+ case ClassComponent:
127
transferOutput(workInProgress.child, workInProgress);
128
// Don't use the state queue to compute the memoized state. We already
129
// merged it and assigned it to the instance. Transfer it from there.
@@ -150,13 +148,8 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
148
workInProgress.callbackList = updateQueue;
149
markCallback(workInProgress);
150
}
153
- const instance = workInProgress.stateNode;
154
- if (typeof instance.getChildContext === 'function') {
155
- popContextProvider();
156
- }
151
return null;
158
- }
159
- case HostContainer: {
152
+ case HostContainer:
153
transferOutput(workInProgress.child, workInProgress);
154
// We don't know if a container has updated any children so we always
155
// need to update it right now. We schedule this side-effect before
@@ -165,8 +158,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
158
// are invoked.
159
markUpdate(workInProgress);
160
return null;
168
- }
169
- case HostComponent: {
161
+ case HostComponent:
162
let newProps = workInProgress.pendingProps;
163
if (current && workInProgress.stateNode != null) {
164
// If we have an alternate, that means this is an update and we need to
@@ -208,8 +200,7 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
200
}
201
workInProgress.memoizedProps = newProps;
202
return null;
211
- }
212
- case HostText: {
203
+ case HostText:
204
let newText = workInProgress.pendingProps;
205
if (current && workInProgress.stateNode != null) {
206
// If we have an alternate, that means this is an update and we need to
@@ -231,32 +222,25 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
222
}
223
workInProgress.memoizedProps = newText;
224
return null;
234
- }
235
- case CoroutineComponent: {
225
+ case CoroutineComponent:
226
return moveCoroutineToHandlerPhase(current, workInProgress);
237
- }
238
- case CoroutineHandlerPhase: {
227
+ case CoroutineHandlerPhase:
228
transferOutput(workInProgress.stateNode, workInProgress);
229
// Reset the tag to now be a first phase coroutine.
230
workInProgress.tag = CoroutineComponent;
231
return null;
243
- }
244
- case YieldComponent: {
232
+ case YieldComponent:
233
// Does nothing.
234
return null;
247
- }
248
- case Fragment: {
235
+ case Fragment:
236
transferOutput(workInProgress.child, workInProgress);
237
return null;
251
- }
238
239
// Error cases
254
- case IndeterminateComponent: {
240
+ case IndeterminateComponent:
241
throw new Error('An indeterminate component should have become determinate before completing.');
256
- }
257
- default: {
242
+ default:
243
throw new Error('Unknown unit of work tag');
259
- }
244
}
245
}
246
src/renderers/shared/fiber/ReactFiberContext.js
+13
-3
@@ -19,6 +19,9 @@ var invariant = require('invariant');
19
var {
20
getComponentName,
21
} = require('ReactFiberTreeReflection');
22
+var {
23
+ ClassComponent,
24
+} = require('ReactTypeOfWork');
25
26
if (__DEV__) {
27
var checkReactTypeSpec = require('checkReactTypeSpec');
@@ -56,12 +59,19 @@ exports.getMaskedContext = function(fiber : Fiber) {
59
return context;
60
};
61
59
-exports.popContextProvider = function() {
62
+exports.isContextProvider = function(fiber : Fiber) : boolean {
63
+ return (
64
+ fiber.tag === ClassComponent &&
65
+ typeof fiber.stateNode.getChildContext === 'function'
66
+ );
67
+};
68
+
69
+exports.popContextProvider = function() : void {
70
stack[index] = emptyObject;
71
index--;
72
};
73
64
-exports.pushContextProvider = function(fiber : Fiber) {
74
+exports.pushContextProvider = function(fiber : Fiber) : void {
75
const instance = fiber.stateNode;
76
const childContextTypes = fiber.type.childContextTypes;
77
const childContext = instance.getChildContext();
@@ -85,7 +95,7 @@ exports.pushContextProvider = function(fiber : Fiber) {
95
stack[index] = mergedContext;
96
};
97
88
-exports.resetContext = function() {
98
+exports.resetContext = function() : void {
99
index = -1;
100
};
101
src/renderers/shared/fiber/ReactFiberScheduler.js
+23
-1
@@ -24,6 +24,13 @@ var ReactCurrentOwner = require('ReactCurrentOwner');
24
25
var { cloneFiber } = require('ReactFiber');
26
27
+var {
28
+ isContextProvider,
29
+ pushContextProvider,
30
+ popContextProvider,
31
+ resetContext,
32
+} = require('ReactFiberContext');
33
+
34
var {
35
NoWork,
36
SynchronousPriority,
@@ -267,6 +274,11 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
274
const current = workInProgress.alternate;
275
const next = completeWork(current, workInProgress);
276
277
+ // We are leaving this subtree, so pop context if any.
278
+ if (isContextProvider(workInProgress)) {
279
+ popContextProvider();
280
+ }
281
+
282
resetWorkPriority(workInProgress);
283
284
// The work is now done. We don't need this anymore. This flags
@@ -345,6 +357,11 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
357
}
358
359
function performUnitOfWork(workInProgress : Fiber) : ?Fiber {
360
+ if (!workInProgress.return) {
361
+ // Don't start new work with context on the stack.
362
+ resetContext();
363
+ }
364
+
365
// The current, flushed, state of this fiber is the alternate.
366
// Ideally nothing should rely on this, but relying on it here
367
// means that we don't need an additional field on the work in
@@ -360,7 +377,12 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
377
ReactFiberInstrumentation.debugTool.onDidBeginWork(workInProgress);
378
}
379
363
- if (!next) {
380
+ if (next) {
381
+ // There is work deeper in the tree, so push the context if it exists.
382
+ if (isContextProvider(workInProgress)) {
383
+ pushContextProvider(workInProgress);
384
+ }
385
+ } else {
386
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
387
ReactFiberInstrumentation.debugTool.onWillCompleteWork(workInProgress);
388
}