@samitouri / QOS-React / commits / f9e1bc9c97

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 }