Skip to content

Commit f273275

Browse files
committed
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.
1 parent 78e68cb commit f273275

5 files changed

Lines changed: 50 additions & 40 deletions

File tree

scripts/fiber/tests-passing.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -820,6 +820,8 @@ src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
820820
* can nest batchedUpdates
821821
* can handle if setState callback throws
822822
* merges and masks context
823+
* does not leak own context into context provider
824+
* provides context when reusing work
823825

824826
src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js
825827
* catches render error in a boundary during mounting

src/renderers/shared/fiber/ReactFiberBeginWork.js

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,6 @@ var {
2727
var ReactTypeOfWork = require('ReactTypeOfWork');
2828
var {
2929
getMaskedContext,
30-
pushContextProvider,
31-
resetContext,
3230
} = require('ReactFiberContext');
3331
var {
3432
IndeterminateComponent,
@@ -189,14 +187,11 @@ module.exports = function<T, P, I, TI, C>(
189187
} else {
190188
shouldUpdate = updateClassInstance(current, workInProgress);
191189
}
192-
const instance = workInProgress.stateNode;
193-
if (typeof instance.getChildContext === 'function') {
194-
pushContextProvider(workInProgress);
195-
}
196190
if (!shouldUpdate) {
197191
return bailoutOnAlreadyFinishedWork(current, workInProgress);
198192
}
199193
// Rerender
194+
const instance = workInProgress.stateNode;
200195
ReactCurrentOwner.current = workInProgress;
201196
const nextChildren = instance.render();
202197
reconcileChildren(current, workInProgress, nextChildren);
@@ -367,9 +362,6 @@ module.exports = function<T, P, I, TI, C>(
367362
}
368363

369364
function beginWork(current : ?Fiber, workInProgress : Fiber, priorityLevel : PriorityLevel) : ?Fiber {
370-
if (!workInProgress.return) {
371-
resetContext();
372-
}
373365
if (workInProgress.pendingWorkPriority === NoWork ||
374366
workInProgress.pendingWorkPriority > priorityLevel) {
375367
return bailoutOnLowPriority(current, workInProgress);

src/renderers/shared/fiber/ReactFiberCompleteWork.js

Lines changed: 11 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import type { HostConfig } from 'ReactFiberReconciler';
1818
import type { ReifiedYield } from 'ReactReifiedYield';
1919

2020
var { reconcileChildFibers } = require('ReactChildFiber');
21-
var { popContextProvider } = require('ReactFiberContext');
2221
var ReactTypeOfWork = require('ReactTypeOfWork');
2322
var ReactTypeOfSideEffect = require('ReactTypeOfSideEffect');
2423
var {
@@ -121,11 +120,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
121120

122121
function completeWork(current : ?Fiber, workInProgress : Fiber) : ?Fiber {
123122
switch (workInProgress.tag) {
124-
case FunctionalComponent: {
123+
case FunctionalComponent:
125124
transferOutput(workInProgress.child, workInProgress);
126125
return null;
127-
}
128-
case ClassComponent: {
126+
case ClassComponent:
129127
transferOutput(workInProgress.child, workInProgress);
130128
// Don't use the state queue to compute the memoized state. We already
131129
// 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>) {
150148
workInProgress.callbackList = updateQueue;
151149
markCallback(workInProgress);
152150
}
153-
const instance = workInProgress.stateNode;
154-
if (typeof instance.getChildContext === 'function') {
155-
popContextProvider();
156-
}
157151
return null;
158-
}
159-
case HostContainer: {
152+
case HostContainer:
160153
transferOutput(workInProgress.child, workInProgress);
161154
// We don't know if a container has updated any children so we always
162155
// 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>) {
165158
// are invoked.
166159
markUpdate(workInProgress);
167160
return null;
168-
}
169-
case HostComponent: {
161+
case HostComponent:
170162
let newProps = workInProgress.pendingProps;
171163
if (current && workInProgress.stateNode != null) {
172164
// 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>) {
208200
}
209201
workInProgress.memoizedProps = newProps;
210202
return null;
211-
}
212-
case HostText: {
203+
case HostText:
213204
let newText = workInProgress.pendingProps;
214205
if (current && workInProgress.stateNode != null) {
215206
// 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>) {
231222
}
232223
workInProgress.memoizedProps = newText;
233224
return null;
234-
}
235-
case CoroutineComponent: {
225+
case CoroutineComponent:
236226
return moveCoroutineToHandlerPhase(current, workInProgress);
237-
}
238-
case CoroutineHandlerPhase: {
227+
case CoroutineHandlerPhase:
239228
transferOutput(workInProgress.stateNode, workInProgress);
240229
// Reset the tag to now be a first phase coroutine.
241230
workInProgress.tag = CoroutineComponent;
242231
return null;
243-
}
244-
case YieldComponent: {
232+
case YieldComponent:
245233
// Does nothing.
246234
return null;
247-
}
248-
case Fragment: {
235+
case Fragment:
249236
transferOutput(workInProgress.child, workInProgress);
250237
return null;
251-
}
252238

253239
// Error cases
254-
case IndeterminateComponent: {
240+
case IndeterminateComponent:
255241
throw new Error('An indeterminate component should have become determinate before completing.');
256-
}
257-
default: {
242+
default:
258243
throw new Error('Unknown unit of work tag');
259-
}
260244
}
261245
}
262246

src/renderers/shared/fiber/ReactFiberContext.js

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,9 @@ var invariant = require('invariant');
1919
var {
2020
getComponentName,
2121
} = require('ReactFiberTreeReflection');
22+
var {
23+
ClassComponent,
24+
} = require('ReactTypeOfWork');
2225

2326
if (__DEV__) {
2427
var checkReactTypeSpec = require('checkReactTypeSpec');
@@ -56,12 +59,19 @@ exports.getMaskedContext = function(fiber : Fiber) {
5659
return context;
5760
};
5861

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 {
6070
stack[index] = emptyObject;
6171
index--;
6272
};
6373

64-
exports.pushContextProvider = function(fiber : Fiber) {
74+
exports.pushContextProvider = function(fiber : Fiber) : void {
6575
const instance = fiber.stateNode;
6676
const childContextTypes = fiber.type.childContextTypes;
6777
const childContext = instance.getChildContext();
@@ -85,7 +95,7 @@ exports.pushContextProvider = function(fiber : Fiber) {
8595
stack[index] = mergedContext;
8696
};
8797

88-
exports.resetContext = function() {
98+
exports.resetContext = function() : void {
8999
index = -1;
90100
};
91101

src/renderers/shared/fiber/ReactFiberScheduler.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,13 @@ var ReactCurrentOwner = require('ReactCurrentOwner');
2424

2525
var { cloneFiber } = require('ReactFiber');
2626

27+
var {
28+
isContextProvider,
29+
pushContextProvider,
30+
popContextProvider,
31+
resetContext,
32+
} = require('ReactFiberContext');
33+
2734
var {
2835
NoWork,
2936
SynchronousPriority,
@@ -267,6 +274,11 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
267274
const current = workInProgress.alternate;
268275
const next = completeWork(current, workInProgress);
269276

277+
// We are leaving this subtree, so pop context if any.
278+
if (isContextProvider(workInProgress)) {
279+
popContextProvider();
280+
}
281+
270282
resetWorkPriority(workInProgress);
271283

272284
// 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>) {
345357
}
346358

347359
function performUnitOfWork(workInProgress : Fiber) : ?Fiber {
360+
if (!workInProgress.return) {
361+
// Don't start new work with context on the stack.
362+
resetContext();
363+
}
364+
348365
// The current, flushed, state of this fiber is the alternate.
349366
// Ideally nothing should rely on this, but relying on it here
350367
// 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>) {
360377
ReactFiberInstrumentation.debugTool.onDidBeginWork(workInProgress);
361378
}
362379

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 {
364386
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
365387
ReactFiberInstrumentation.debugTool.onWillCompleteWork(workInProgress);
366388
}

0 commit comments

Comments
 (0)