@samitouri / QOS-React-2 / commits / eec431297a

Priority context during reconciliation

setState inside render/cWRP should have the same priority as whatever level is currently being reconciled.

Andrew Clark committed Dec 13, 2016 at 15:05 UTC eec431297a145ccc0159ce3a1bf093e47c9c88d5
6 files changed +120 -73
scripts/fiber/tests-passing.txt
+1
@@ -1237,6 +1237,7 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalUpdates-test.js
1237 * can abort an update, schedule additional updates, and resume
1238 * can abort an update, schedule a replaceState, and resume
1239 * does not call callbacks that are scheduled by another callback until a later commit
1240 +* gives setState during reconciliation the same priority as whatever level is currently reconciling
1241 * enqueues setState inside an updater function as if the in-progress update is progressed (and warns)
1242
1243 src/renderers/shared/fiber/__tests__/ReactTopLevelFragment-test.js
src/renderers/shared/fiber/ReactFiberBeginWork.js
+10 -3
@@ -71,8 +71,10 @@ if (__DEV__) {
71 module.exports = function<T, P, I, TI, C, CX>(
72 config : HostConfig<T, P, I, TI, C, CX>,
73 hostContext : HostContext<C, CX>,
74 - scheduleUpdateAtPriority : (fiber: Fiber, priorityLevel : PriorityLevel) => void,
75 - getPriorityContext : () => PriorityLevel
74 + scheduleSetState: (fiber : Fiber, partialState : any) => void,
75 + scheduleReplaceState: (fiber : Fiber, state : any) => void,
76 + scheduleForceUpdate: (fiber : Fiber) => void,
77 + scheduleUpdateCallback: (fiber : Fiber, callback : Function) => void,
78 ) {
79
80 const { shouldSetTextContent } = config;
@@ -89,7 +91,12 @@ module.exports = function<T, P, I, TI, C, CX>(
91 mountClassInstance,
92 resumeMountClassInstance,
93 updateClassInstance,
92 - } = ReactFiberClassComponent(scheduleUpdateAtPriority, getPriorityContext);
94 + } = ReactFiberClassComponent(
95 + scheduleSetState,
96 + scheduleReplaceState,
97 + scheduleForceUpdate,
98 + scheduleUpdateCallback
99 + );
100
101 function markChildAsProgressed(current, workInProgress, priorityLevel) {
102 // We now have clones. Let's store them as the currently progressed work.
src/renderers/shared/fiber/ReactFiberClassComponent.js
+8 -18
@@ -19,10 +19,6 @@ var {
19 getMaskedContext,
20 } = require('ReactFiberContext');
21 var {
22 - addUpdate,
23 - addReplaceUpdate,
24 - addForceUpdate,
25 - addCallback,
22 beginUpdateQueue,
23 } = require('ReactFiberUpdateQueue');
24 var { hasContextChanged } = require('ReactFiberContext');
@@ -36,8 +32,10 @@ var invariant = require('invariant');
32 const isArray = Array.isArray;
33
34 module.exports = function(
39 - scheduleUpdateAtPriority : (fiber: Fiber, priorityLevel : PriorityLevel) => void,
40 - getPriorityContext : () => PriorityLevel,
35 + scheduleSetState: (fiber : Fiber, partialState : any) => void,
36 + scheduleReplaceState: (fiber : Fiber, state : any) => void,
37 + scheduleForceUpdate: (fiber : Fiber) => void,
38 + scheduleUpdateCallback: (fiber : Fiber, callback : Function) => void,
39 ) {
40
41 // Class component state updater
@@ -45,27 +43,19 @@ module.exports = function(
43 isMounted,
44 enqueueSetState(instance, partialState) {
45 const fiber = ReactInstanceMap.get(instance);
48 - const priorityLevel = getPriorityContext();
49 - addUpdate(fiber, partialState, priorityLevel);
50 - scheduleUpdateAtPriority(fiber, priorityLevel);
46 + scheduleSetState(fiber, partialState);
47 },
48 enqueueReplaceState(instance, state) {
49 const fiber = ReactInstanceMap.get(instance);
54 - const priorityLevel = getPriorityContext();
55 - addReplaceUpdate(fiber, state, priorityLevel);
56 - scheduleUpdateAtPriority(fiber, priorityLevel);
50 + scheduleReplaceState(fiber, state);
51 },
52 enqueueForceUpdate(instance) {
53 const fiber = ReactInstanceMap.get(instance);
60 - const priorityLevel = getPriorityContext();
61 - addForceUpdate(fiber, priorityLevel);
62 - scheduleUpdateAtPriority(fiber, priorityLevel);
54 + scheduleForceUpdate(fiber);
55 },
56 enqueueCallback(instance, callback) {
57 const fiber = ReactInstanceMap.get(instance);
66 - const priorityLevel = getPriorityContext();
67 - addCallback(fiber, callback, priorityLevel);
68 - scheduleUpdateAtPriority(fiber, priorityLevel);
58 + scheduleUpdateCallback(fiber, callback);
59 },
60 };
61
src/renderers/shared/fiber/ReactFiberReconciler.js
+3 -7
@@ -24,8 +24,6 @@ var {
24 var { createFiberRoot } = require('ReactFiberRoot');
25 var ReactFiberScheduler = require('ReactFiberScheduler');
26
27 -var { addCallback } = require('ReactFiberUpdateQueue');
28 -
27 if (__DEV__) {
28 var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
29 }
@@ -100,7 +98,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
98
99 var {
100 scheduleWork,
103 - getPriorityContext,
101 + scheduleUpdateCallback,
102 performWithPriority,
103 batchedUpdates,
104 syncUpdates,
@@ -123,8 +121,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
121 current.alternate.pendingProps = element;
122 }
123 if (callback) {
126 - const priorityLevel = getPriorityContext();
127 - addCallback(current, callback, priorityLevel);
124 + scheduleUpdateCallback(current, callback);
125 }
126
127 scheduleWork(root);
@@ -154,8 +151,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
151 current.alternate.pendingProps = element;
152 }
153 if (callback) {
157 - const priorityLevel = getPriorityContext();
158 - addCallback(current, callback, priorityLevel);
154 + scheduleUpdateCallback(current, callback);
155 }
156
157 scheduleWork(root);
src/renderers/shared/fiber/ReactFiberScheduler.js
+56 -12
@@ -56,6 +56,10 @@ var {
56
57 var {
58 getPendingPriority,
59 + addUpdate,
60 + addReplaceUpdate,
61 + addForceUpdate,
62 + addCallback,
63 } = require('ReactFiberUpdateQueue');
64
65 var {
@@ -72,8 +76,14 @@ var timeHeuristicForUnitOfWork = 1;
76 module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) {
77 const hostContext = ReactFiberHostContext(config);
78 const { popHostContainer, popHostContext, resetHostContainer } = hostContext;
75 - const { beginWork, beginFailedWork } =
76 - ReactFiberBeginWork(config, hostContext, scheduleUpdateAtPriority, getPriorityContext);
79 + const { beginWork, beginFailedWork } = ReactFiberBeginWork(
80 + config,
81 + hostContext,
82 + scheduleSetState,
83 + scheduleReplaceState,
84 + scheduleForceUpdate,
85 + scheduleUpdateCallback,
86 + );
87 const { completeWork } = ReactFiberCompleteWork(config, hostContext);
88 const {
89 commitPlacement,
@@ -95,6 +105,10 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
105 SynchronousPriority :
106 LowPriority;
107
108 + // Keep track of this so we can reset the priority context if an error
109 + // is thrown during reconciliation.
110 + let priorityContextBeforeReconciliation : PriorityLevel = NoWork;
111 +
112 // Keeps track of whether we're currently in a work loop. Used to batch
113 // nested updates.
114 let isPerformingWork : boolean = false;
@@ -145,15 +159,6 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
159 }
160 }
161
148 - function getPriorityContext() : PriorityLevel {
149 - let priorityLevel = priorityContext;
150 - // If we're in a batch, downgrade sync priority to task priority
151 - if (priorityLevel === SynchronousPriority && isPerformingWork) {
152 - priorityLevel = TaskPriority;
153 - }
154 - return priorityLevel;
155 - }
156 -
162 function findNextUnitOfWork() {
163 // Clear out roots with no more work on them, or if they have uncaught errors
164 while (nextScheduledRoot && nextScheduledRoot.current.pendingWorkPriority === NoWork) {
@@ -481,6 +486,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
486 }
487
488 function performUnitOfWork(workInProgress : Fiber) : ?Fiber {
489 +
490 // The current, flushed, state of this fiber is the alternate.
491 // Ideally nothing should rely on this, but relying on it here
492 // means that we don't need an additional field on the work in
@@ -491,7 +497,11 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
497 ReactFiberInstrumentation.debugTool.onWillBeginWork(workInProgress);
498 }
499 // See if beginning this work spawns more work.
500 +
501 + priorityContextBeforeReconciliation = priorityContext;
502 + priorityContext = nextPriorityLevel;
503 let next = beginWork(current, workInProgress, nextPriorityLevel);
504 + priorityContext = priorityContextBeforeReconciliation;
505
506 if (__DEV__ && ReactFiberInstrumentation.debugTool) {
507 ReactFiberInstrumentation.debugTool.onDidBeginWork(workInProgress);
@@ -513,10 +523,13 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
523 ReactDebugCurrentFiber.current = null;
524 }
525
526 +
527 +
528 return next;
529 }
530
531 function performFailedUnitOfWork(workInProgress : Fiber) : ?Fiber {
532 +
533 // The current, flushed, state of this fiber is the alternate.
534 // Ideally nothing should rely on this, but relying on it here
535 // means that we don't need an additional field on the work in
@@ -527,7 +540,10 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
540 ReactFiberInstrumentation.debugTool.onWillBeginWork(workInProgress);
541 }
542 // See if beginning this work spawns more work.
543 + priorityContextBeforeReconciliation = priorityContext;
544 + priorityContext = nextPriorityLevel;
545 let next = beginFailedWork(current, workInProgress, nextPriorityLevel);
546 + priorityContext = priorityContextBeforeReconciliation;
547
548 if (__DEV__ && ReactFiberInstrumentation.debugTool) {
549 ReactFiberInstrumentation.debugTool.onDidBeginWork(workInProgress);
@@ -686,6 +702,9 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
702 // We caught an error during either the begin or complete phases.
703 const failedWork = nextUnitOfWork;
704
705 + // Reset the priority context to its value before reconcilation.
706 + priorityContext = priorityContextBeforeReconciliation;
707 +
708 // "Capture" the error by finding the nearest boundary. If there is no
709 // error boundary, the nearest host container acts as one. If
710 // captureError returns null, the error was intentionally ignored.
@@ -1002,6 +1021,11 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
1021 }
1022
1023 function scheduleUpdateAtPriority(fiber : Fiber, priorityLevel : PriorityLevel) {
1024 + // If we're in a batch, downgrade sync priority to task priority
1025 + if (priorityLevel === SynchronousPriority && isPerformingWork) {
1026 + priorityLevel = TaskPriority;
1027 + }
1028 +
1029 let node = fiber;
1030 let shouldContinue = true;
1031 while (node && shouldContinue) {
@@ -1040,6 +1064,26 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
1064 scheduleUpdateAtPriority(fiber, TaskPriority);
1065 }
1066
1067 + function scheduleSetState(fiber : Fiber, partialState : any) {
1068 + addUpdate(fiber, partialState, priorityContext);
1069 + scheduleUpdateAtPriority(fiber, priorityContext);
1070 + }
1071 +
1072 + function scheduleReplaceState(fiber : Fiber, state : any) {
1073 + addReplaceUpdate(fiber, state, priorityContext);
1074 + scheduleUpdateAtPriority(fiber, priorityContext);
1075 + }
1076 +
1077 + function scheduleForceUpdate(fiber : Fiber) {
1078 + addForceUpdate(fiber, priorityContext);
1079 + scheduleUpdateAtPriority(fiber, priorityContext);
1080 + }
1081 +
1082 + function scheduleUpdateCallback(fiber : Fiber, callback : Function) {
1083 + addCallback(fiber, callback, priorityContext);
1084 + scheduleUpdateAtPriority(fiber, priorityContext);
1085 + }
1086 +
1087 function performWithPriority(priorityLevel : PriorityLevel, fn : Function) {
1088 const previousPriorityContext = priorityContext;
1089 priorityContext = priorityLevel;
@@ -1088,7 +1132,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
1132
1133 return {
1134 scheduleWork: scheduleWork,
1091 - getPriorityContext: getPriorityContext,
1135 + scheduleUpdateCallback: scheduleUpdateCallback,
1136 performWithPriority: performWithPriority,
1137 batchedUpdates: batchedUpdates,
1138 syncUpdates: syncUpdates,
src/renderers/shared/fiber/__tests__/ReactIncrementalUpdates-test.js
+42 -33
@@ -246,21 +246,15 @@ describe('ReactIncrementalUpdates', () => {
246 ]);
247 });
248
249 - it('enqueues setState inside an updater function as if the in-progress update is progressed (and warns)', () => {
250 - spyOn(console, 'error');
249 + it('gives setState during reconciliation the same priority as whatever level is currently reconciling', () => {
250 let instance;
251 let ops = [];
252 +
253 class Foo extends React.Component {
254 state = {};
255 - componentDidMount() {
256 - ops.push('componentDidMount');
257 - this.setState(function a() {
258 - // Force update b to have Task priority
259 - ReactNoop.syncUpdates(() => {
260 - this.setState({ b: 'b' });
261 - });
262 - return { a: 'a' };
263 - });
255 + componentWillReceiveProps() {
256 + ops.push('componentWillReceiveProps');
257 + this.setState({ b: 'b' });
258 }
259 render() {
260 ops.push('render');
@@ -268,44 +262,59 @@ describe('ReactIncrementalUpdates', () => {
262 return <span prop={Object.keys(this.state).join('')} />;
263 }
264 }
271 -
265 ReactNoop.render(<Foo />);
266 ReactNoop.flush();
274 - expectDev(console.error.calls.count()).toBe(1);
267 +
268 + ops = [];
269 +
270 + ReactNoop.performAnimationWork(() => {
271 + instance.setState({ a: 'a' });
272 + ReactNoop.render(<Foo />); // Trigger componentWillReceiveProps
273 + });
274 + ReactNoop.flush();
275 +
276 expect(ReactNoop.getChildren()).toEqual([span('ab')]);
277 expect(ops).toEqual([
277 - // Initial render
278 - 'render',
279 - 'componentDidMount',
280 - // Updates a and b both have Task priority. Update b is enqueued while
281 - // update a is being processed, but it should be inserted into the queue
282 - // as if update a is already processed. Then processing continues. Because
283 - // they have the same priority, update b is processed in the same batch.
284 - // So there should only be a single render below.
278 + 'componentWillReceiveProps',
279 'render',
280 ]);
281 + });
282
288 - ops = [];
283
290 - ReactNoop.performAnimationWork(() => {
291 - instance.setState(function c() {
292 - // Update d happens during the begin phase, so it has low priority.
293 - this.setState({ d: 'd' });
294 - return { c: 'c' };
295 - });
284 + it('enqueues setState inside an updater function as if the in-progress update is progressed (and warns)', () => {
285 + spyOn(console, 'error');
286 + let instance;
287 + let ops = [];
288 + class Foo extends React.Component {
289 + state = {};
290 + render() {
291 + ops.push('render');
292 + instance = this;
293 + return <span prop={Object.keys(this.state).join('')} />;
294 + }
295 + }
296 +
297 + ReactNoop.render(<Foo />);
298 + ReactNoop.flush();
299 +
300 + instance.setState(function a() {
301 + ops.push('setState updater');
302 + this.setState({ b: 'b' });
303 + return { a: 'a' };
304 });
305
306 ReactNoop.flush();
299 - expect(ReactNoop.getChildren()).toEqual([span('abcd')]);
307 + expect(ReactNoop.getChildren()).toEqual([span('ab')]);
308 expect(ops).toEqual([
301 - // Update c has animation priority. Update d is enqueued while c is being
302 - // processed with animation priority. Because d is low priority, it is not
303 - // processed until the next render. So there should be two renders below.
309 + // Initial render
310 'render',
311 + 'setState updater',
312 + // Update b is enqueued with the same priority as update a, so it should
313 + // be flushed in the same commit.
314 'render',
315 ]);
316
308 - expectDev(console.error.calls.count()).toBe(2);
317 + expectDev(console.error.calls.count()).toBe(1);
318 console.error.calls.reset();
319 });
320 });