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

Use an UpdateQueue for top-level updates

...rather than mutate the pendingProps directly, which throws away any previously scheduled work.

Andrew Clark committed Dec 15, 2016 at 13:58 UTC c6ccc190950355b96093418fb948ab19c101528a
8 files changed +119 -465
scripts/fiber/tests-passing.txt
+5 -19
@@ -1190,27 +1190,13 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalReflection-test.js
1190 * finds no node before insertion and correct node before deletion
1191
1192 src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js
1193 -* schedules and flushes animation work
1194 -* schedules and flushes animation work for many roots
1195 -* flushes all scheduled animation work
1196 -* flushes all scheduled animation work for many roots
1193 * schedules and flushes deferred work
1198 -* schedules and flushes deferred work for many roots
1199 -* flushes scheduled deferred work fitting within deadline
1200 -* flushes scheduled deferred work fitting within deadline for many roots
1201 -* schedules more deferred work if it runs out of time
1202 -* schedules more deferred work if it runs out of time with many roots
1203 -* flushes late animation work in a deferred callback if it wins
1204 -* flushes late animation work in a deferred callback if it wins with many roots
1205 -* flushes late animation work in an animation callback if it wins
1206 -* flushes late animation work in an animation callback if it wins with many roots
1207 -* flushes all work in a deferred callback if it wins
1208 -* flushes all work in a deferred callback if it wins with many roots
1209 -* flushes root with late deferred work in an animation callback if it wins
1210 -* flushes all roots with animation work in an animation callback if it wins
1211 -* splits deferred work on multiple roots
1194 +* schedules and flushes animation work
1195 +* searches for work on other roots once the current root completes
1196 +* schedules an animation callback when there`s leftover animation work
1197 +* schedules top-level updates in order of priority
1198 +* schedules top-level updates with same priority in order of insertion
1199 * works on deferred roots in the order they were scheduled
1213 -* handles interleaved deferred and animation work
1200 * schedules sync updates when inside componentDidMount/Update
1201 * can opt-in to deferred/animation scheduling inside componentDidMount/Update
1202 * performs Task work even after time runs out
src/renderers/noop/ReactNoop.js
+4 -2
@@ -304,14 +304,16 @@ var ReactNoop = {
304 log(
305 ' '.repeat(depth + 1) + '~',
306 firstUpdate && firstUpdate.partialState,
307 - firstUpdate.callback ? 'with callback' : ''
307 + firstUpdate.callback ? 'with callback' : '',
308 + '[' + firstUpdate.priorityLevel + ']'
309 );
310 var next;
311 while (next = firstUpdate.next) {
312 log(
313 ' '.repeat(depth + 1) + '~',
314 next.partialState,
314 - next.callback ? 'with callback' : ''
315 + next.callback ? 'with callback' : '',
316 + '[' + firstUpdate.priorityLevel + ']'
317 );
318 }
319 }
src/renderers/shared/fiber/ReactFiberBeginWork.js
+7 -4
@@ -542,13 +542,16 @@ module.exports = function<T, P, I, TI, C, CX>(
542 pushTopLevelContextObject(root.context, false);
543 }
544
545 + pushHostContainer(root.containerInfo);
546 +
547 if (updateQueue) {
546 - beginUpdateQueue(workInProgress, updateQueue, null, null, null, priorityLevel);
548 + const prevState = workInProgress.memoizedState;
549 + const state = beginUpdateQueue(workInProgress, updateQueue, null, prevState, null, priorityLevel);
550 + const element = state.element;
551 + reconcileChildren(current, workInProgress, element);
552 + workInProgress.memoizedState = state;
553 }
554
549 - pushHostContainer(workInProgress.stateNode.containerInfo);
550 - reconcileChildren(current, workInProgress, pendingProps);
551 -
555 // A yield component is just a placeholder, we can just run through the
556 // next one immediately.
557 return workInProgress.child;
src/renderers/shared/fiber/ReactFiberReconciler.js
+5 -26
@@ -98,7 +98,7 @@ getContextForSubtree._injectFiber(function(fiber : Fiber) {
98 module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) : Reconciler<C, I, TI> {
99
100 var {
101 - scheduleWork,
101 + scheduleSetState,
102 scheduleUpdateCallback,
103 performWithPriority,
104 batchedUpdates,
@@ -113,20 +113,11 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
113 const root = createFiberRoot(containerInfo, context);
114 const current = root.current;
115
116 - // TODO: Use the updateQueue and scheduleUpdate, instead of pendingProps.
117 - // TODO: This should not override the pendingWorkPriority if there is
118 - // higher priority work in the subtree.
119 -
120 - current.pendingProps = element;
121 - if (current.alternate) {
122 - current.alternate.pendingProps = element;
123 - }
116 + scheduleSetState(current, { element });
117 if (callback) {
118 scheduleUpdateCallback(current, callback);
119 }
120
128 - scheduleWork(root);
129 -
121 if (__DEV__ && ReactFiberInstrumentation.debugTool) {
122 ReactFiberInstrumentation.debugTool.onMountContainer(root);
123 }
@@ -144,19 +135,11 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
135
136 root.pendingContext = getContextForSubtree(parentComponent);
137
147 - // TODO: Use the updateQueue and scheduleUpdate, instead of pendingProps.
148 - // TODO: This should not override the pendingWorkPriority if there is
149 - // higher priority work in the subtree.
150 - current.pendingProps = element;
151 - if (current.alternate) {
152 - current.alternate.pendingProps = element;
153 - }
138 + scheduleSetState(current, { element });
139 if (callback) {
140 scheduleUpdateCallback(current, callback);
141 }
142
158 - scheduleWork(root);
159 -
143 if (__DEV__ && ReactFiberInstrumentation.debugTool) {
144 ReactFiberInstrumentation.debugTool.onUpdateContainer(root);
145 }
@@ -165,13 +148,9 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
148 unmountContainer(container : OpaqueNode) : void {
149 // TODO: If this is a nested container, this won't be the root.
150 const root : FiberRoot = (container.stateNode : any);
168 - // TODO: Use pending work/state instead of props.
169 - root.current.pendingProps = [];
170 - if (root.current.alternate) {
171 - root.current.alternate.pendingProps = [];
172 - }
151 + const current = root.current;
152
174 - scheduleWork(root);
153 + scheduleSetState(current, { element: [] });
154
155 if (__DEV__ && ReactFiberInstrumentation.debugTool) {
156 ReactFiberInstrumentation.debugTool.onUnmountContainer(root);
src/renderers/shared/fiber/ReactFiberScheduler.js
+29 -48
@@ -944,28 +944,10 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
944 }
945 }
946
947 - function scheduleWork(root : FiberRoot) {
948 - let priorityLevel = priorityContext;
949 -
950 - // If we're in a batch, switch to task priority
951 - if (priorityLevel === SynchronousPriority && isPerformingWork) {
952 - priorityLevel = TaskPriority;
953 - }
954 -
955 - scheduleWorkAtPriority(root, priorityLevel);
956 - }
957 -
958 - function scheduleWorkAtPriority(root : FiberRoot, priorityLevel : PriorityLevel) {
959 - // Set the priority on the root, without deprioritizing
960 - if (root.current.pendingWorkPriority === NoWork ||
961 - priorityLevel <= root.current.pendingWorkPriority) {
962 - root.current.pendingWorkPriority = priorityLevel;
963 - }
964 - if (root.current.alternate) {
965 - if (root.current.alternate.pendingWorkPriority === NoWork ||
966 - priorityLevel <= root.current.alternate.pendingWorkPriority) {
967 - root.current.alternate.pendingWorkPriority = priorityLevel;
968 - }
947 + function scheduleRoot(root : FiberRoot) {
948 + const priorityLevel = root.current.pendingWorkPriority;
949 + if (priorityLevel === NoWork) {
950 + return;
951 }
952
953 if (!root.isScheduled) {
@@ -987,30 +969,6 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
969 // priority work somewhere earlier than before.
970 nextUnitOfWork = null;
971 }
990 -
991 - // Depending on the priority level, either perform work now or schedule
992 - // a callback to perform work later.
993 - switch (priorityLevel) {
994 - case SynchronousPriority:
995 - // Perform work immediately
996 - performWork(SynchronousPriority);
997 - return;
998 - case TaskPriority:
999 - // If we're already performing work, Task work will be flushed before
1000 - // exiting the current batch. So we can skip it here.
1001 - if (!isPerformingWork) {
1002 - performWork(TaskPriority);
1003 - }
1004 - return;
1005 - case AnimationPriority:
1006 - scheduleAnimationCallback(performAnimationWork);
1007 - return;
1008 - case HighPriority:
1009 - case LowPriority:
1010 - case OffscreenPriority:
1011 - scheduleDeferredCallback(performDeferredWork);
1012 - return;
1013 - }
972 }
973
974 function scheduleUpdateAtPriority(fiber : Fiber, priorityLevel : PriorityLevel) {
@@ -1043,7 +1001,30 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
1001 if (!node.return) {
1002 if (node.tag === HostRoot) {
1003 const root : FiberRoot = (node.stateNode : any);
1046 - scheduleWorkAtPriority(root, priorityLevel);
1004 + scheduleRoot(root, priorityLevel);
1005 + // Depending on the priority level, either perform work now or
1006 + // schedule a callback to perform work later.
1007 + switch (priorityLevel) {
1008 + case SynchronousPriority:
1009 + // Perform work immediately
1010 + performWork(SynchronousPriority);
1011 + return;
1012 + case TaskPriority:
1013 + // If we're already performing work, Task work will be flushed before
1014 + // exiting the current batch. So we can skip it here.
1015 + if (!isPerformingWork) {
1016 + performWork(TaskPriority);
1017 + }
1018 + return;
1019 + case AnimationPriority:
1020 + scheduleAnimationCallback(performAnimationWork);
1021 + return;
1022 + case HighPriority:
1023 + case LowPriority:
1024 + case OffscreenPriority:
1025 + scheduleDeferredCallback(performDeferredWork);
1026 + return;
1027 + }
1028 } else {
1029 // TODO: Warn about setting state on an unmounted component.
1030 return;
@@ -1124,7 +1105,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
1105 }
1106
1107 return {
1127 - scheduleWork: scheduleWork,
1108 + scheduleSetState: scheduleSetState,
1109 scheduleUpdateCallback: scheduleUpdateCallback,
1110 performWithPriority: performWithPriority,
1111 batchedUpdates: batchedUpdates,
src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
-1
@@ -267,7 +267,6 @@ describe('ReactIncremental', () => {
267 ReactNoop.performAnimationWork(() => {
268 ReactNoop.render(<Foo text="foo" />);
269 });
270 - ReactNoop.render(<Foo text="bar" />);
270 ReactNoop.flushAnimationPri();
271
272 expect(ops).toEqual(['Foo', 'Bar', 'Bar']);
src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js
+51 -357
@@ -25,351 +25,93 @@ describe('ReactIncrementalScheduling', () => {
25 return { type: 'span', children: [], prop };
26 }
27
28 - it('schedules and flushes animation work', () => {
29 - ReactNoop.performAnimationWork(() => {
30 - ReactNoop.render(<span prop="1" />);
31 - });
28 + it('schedules and flushes deferred work', () => {
29 + ReactNoop.render(<span prop="1" />);
30 expect(ReactNoop.getChildren()).toEqual([]);
31
34 - ReactNoop.flushAnimationPri();
32 + ReactNoop.flushDeferredPri();
33 expect(ReactNoop.getChildren()).toEqual([span('1')]);
34 });
35
38 - it('schedules and flushes animation work for many roots', () => {
39 - ReactNoop.performAnimationWork(() => {
40 - ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
41 - ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
42 - ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
43 - });
44 - expect(ReactNoop.getChildren('a')).toEqual([]);
45 - expect(ReactNoop.getChildren('b')).toEqual([]);
46 - expect(ReactNoop.getChildren('c')).toEqual([]);
47 -
48 - ReactNoop.flushAnimationPri();
49 - expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
50 - expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
51 - expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
52 - });
53 -
54 - it('flushes all scheduled animation work', () => {
36 + it('schedules and flushes animation work', () => {
37 ReactNoop.performAnimationWork(() => {
38 ReactNoop.render(<span prop="1" />);
39 });
58 - ReactNoop.performAnimationWork(() => {
59 - ReactNoop.render(<span prop="2" />);
60 - });
40 expect(ReactNoop.getChildren()).toEqual([]);
41
42 ReactNoop.flushAnimationPri();
64 - expect(ReactNoop.getChildren()).toEqual([span('2')]);
65 - });
66 -
67 - it('flushes all scheduled animation work for many roots', () => {
68 - ReactNoop.performAnimationWork(() => {
69 - ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
70 - ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
71 - ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
72 - });
73 - ReactNoop.performAnimationWork(() => {
74 - ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
75 - ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
76 - ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
77 - });
78 - expect(ReactNoop.getChildren('a')).toEqual([]);
79 - expect(ReactNoop.getChildren('b')).toEqual([]);
80 - expect(ReactNoop.getChildren('c')).toEqual([]);
81 -
82 - ReactNoop.flushAnimationPri();
83 - expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
84 - expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
85 - expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
86 - });
87 -
88 - it('schedules and flushes deferred work', () => {
89 - ReactNoop.render(<span prop="1" />);
90 - expect(ReactNoop.getChildren()).toEqual([]);
91 -
92 - ReactNoop.flushDeferredPri();
43 expect(ReactNoop.getChildren()).toEqual([span('1')]);
44 });
45
96 - it('schedules and flushes deferred work for many roots', () => {
46 + it('searches for work on other roots once the current root completes', () => {
47 ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
48 ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
49 ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
100 - expect(ReactNoop.getChildren('a')).toEqual([]);
101 - expect(ReactNoop.getChildren('b')).toEqual([]);
102 - expect(ReactNoop.getChildren('c')).toEqual([]);
50
104 - ReactNoop.flushDeferredPri();
51 + ReactNoop.flush();
52 +
53 expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
54 expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
55 expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
56 });
57
110 - it('flushes scheduled deferred work fitting within deadline', () => {
111 - ReactNoop.render(<span prop="1" />);
112 - ReactNoop.render(<span prop="2" />);
113 - expect(ReactNoop.getChildren()).toEqual([]);
114 -
115 - ReactNoop.flushDeferredPri();
116 - expect(ReactNoop.getChildren()).toEqual([span('2')]);
117 - });
118 -
119 - it('flushes scheduled deferred work fitting within deadline for many roots', () => {
120 - ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
121 - ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
122 - ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
123 - ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
124 - ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
125 - ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
126 - expect(ReactNoop.getChildren('a')).toEqual([]);
127 - expect(ReactNoop.getChildren('b')).toEqual([]);
128 - expect(ReactNoop.getChildren('c')).toEqual([]);
129 -
130 - ReactNoop.flushDeferredPri();
131 - expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
132 - expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
133 - expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
134 - });
135 -
136 - it('schedules more deferred work if it runs out of time', () => {
137 - ReactNoop.render(<span prop="1" />);
138 - ReactNoop.render(<span prop="2" />);
139 - expect(ReactNoop.getChildren()).toEqual([]);
140 -
141 - ReactNoop.flushDeferredPri(5);
142 - expect(ReactNoop.getChildren()).toEqual([]);
143 -
144 - ReactNoop.flushDeferredPri(10);
145 - expect(ReactNoop.getChildren()).toEqual([]);
146 -
147 - ReactNoop.flushDeferredPri(10 + 5);
148 - expect(ReactNoop.getChildren()).toEqual([span('2')]);
149 - });
150 -
151 - it('schedules more deferred work if it runs out of time with many roots', () => {
152 - ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
153 - ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
154 - ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
155 - ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
156 - ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
157 - ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
158 - expect(ReactNoop.getChildren('a')).toEqual([]);
159 - expect(ReactNoop.getChildren('b')).toEqual([]);
160 - expect(ReactNoop.getChildren('c')).toEqual([]);
161 -
162 - ReactNoop.flushDeferredPri(15 + 5);
163 - expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
164 - expect(ReactNoop.getChildren('b')).toEqual([]);
165 - expect(ReactNoop.getChildren('c')).toEqual([]);
166 -
167 - ReactNoop.flushDeferredPri(15 + 5);
168 - expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
169 - expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
170 - expect(ReactNoop.getChildren('c')).toEqual([]);
171 -
172 - ReactNoop.flushDeferredPri(15 + 5);
173 - expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
174 - expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
175 - expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
176 - });
177 -
178 - it('flushes late animation work in a deferred callback if it wins', () => {
179 - // Schedule early deferred
180 - ReactNoop.render(<span prop="1" />);
181 - // Schedule late animation
182 - ReactNoop.performAnimationWork(() => {
183 - ReactNoop.render(<span prop="2" />);
184 - });
185 - expect(ReactNoop.getChildren()).toEqual([]);
186 -
187 - // We only scheduled deferred callback so that's what we get.
188 - // It will flush everything.
189 - ReactNoop.flushDeferredPri();
190 - expect(ReactNoop.getChildren()).toEqual([span('2')]);
191 - });
192 -
193 - it('flushes late animation work in a deferred callback if it wins with many roots', () => {
194 - // Schedule early deferred
195 - ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
196 - ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
197 - // Schedule late animation
198 - ReactNoop.performAnimationWork(() => {
199 - ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
200 - ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
201 - });
202 - expect(ReactNoop.getChildren('a')).toEqual([]);
203 - expect(ReactNoop.getChildren('b')).toEqual([]);
204 - expect(ReactNoop.getChildren('c')).toEqual([]);
205 -
206 - // We only scheduled deferred callback so that's what we get.
207 - // It will flush everything.
208 - ReactNoop.flushDeferredPri();
209 - expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
210 - expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
211 - expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
212 - });
213 -
214 - it('flushes late animation work in an animation callback if it wins', () => {
215 - // Schedule early deferred
216 - ReactNoop.render(<span prop="1" />);
217 - // Schedule late animation
218 - ReactNoop.performAnimationWork(() => {
219 - ReactNoop.render(<span prop="2" />);
220 - });
221 - expect(ReactNoop.getChildren()).toEqual([]);
222 -
223 - // Flushing animation should have flushed the animation.
224 - ReactNoop.flushAnimationPri();
225 - expect(ReactNoop.getChildren()).toEqual([span('2')]);
226 - });
58 + it('schedules an animation callback when there`\s leftover animation work', () => {
59 + class Foo extends React.Component {
60 + state = { step: 0 };
61 + componentDidMount() {
62 + ReactNoop.performAnimationWork(() => {
63 + this.setState({ step: 2 });
64 + });
65 + this.setState({ step: 1 });
66 + }
67 + render() {
68 + return <span prop={this.state.step} />;
69 + }
70 + }
71
228 - it('flushes late animation work in an animation callback if it wins with many roots', () => {
229 - // Schedule early deferred
230 - ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
231 - ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
232 - // Schedule late animation
233 - ReactNoop.performAnimationWork(() => {
234 - ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
235 - ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
236 - });
237 - expect(ReactNoop.getChildren('a')).toEqual([]);
238 - expect(ReactNoop.getChildren('b')).toEqual([]);
239 - expect(ReactNoop.getChildren('c')).toEqual([]);
72 + ReactNoop.render(<Foo />);
73 + // Flush just enough work to mount the component, but not enough to flush
74 + // the animation update.
75 + ReactNoop.flushDeferredPri(25);
76 + expect(ReactNoop.getChildren()).toEqual([span(1)]);
77
241 - // Flushing animation should have flushed the animation.
78 + // There's more animation work. A callback should have been scheduled.
79 ReactNoop.flushAnimationPri();
243 - expect(ReactNoop.getChildren('a')).toEqual([]);
244 - expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
245 - expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
246 -
247 - ReactNoop.flushDeferredPri();
248 - expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
249 - expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
250 - expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
80 + expect(ReactNoop.getChildren()).toEqual([span(2)]);
81 });
82
253 - it('flushes all work in a deferred callback if it wins', () => {
254 - // Schedule early animation
255 - ReactNoop.performAnimationWork(() => {
256 - ReactNoop.render(<span prop="1" />);
257 - });
258 - // Schedule late deferred
259 - ReactNoop.render(<span prop="2" />);
260 - expect(ReactNoop.getChildren()).toEqual([]);
261 -
262 - // Flushing deferred should have flushed both early animation and late deferred work that invalidated it.
263 - // This is not a common case, as animation should generally be flushed before deferred work.
264 - ReactNoop.flushDeferredPri();
265 - expect(ReactNoop.getChildren()).toEqual([span('2')]);
266 - });
267 -
268 - it('flushes all work in a deferred callback if it wins with many roots', () => {
269 - // Schedule early animation
270 - ReactNoop.performAnimationWork(() => {
271 - ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
272 - ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
273 - });
274 - // Schedule late deferred
275 - ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
276 - ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
277 - expect(ReactNoop.getChildren('a')).toEqual([]);
278 - expect(ReactNoop.getChildren('b')).toEqual([]);
279 - expect(ReactNoop.getChildren('c')).toEqual([]);
280 -
281 - // Flushing deferred should have flushed both early animation and late deferred work that invalidated it.
282 - // This is not a common case, as animation should generally be flushed before deferred work.
283 - ReactNoop.flushDeferredPri();
284 - expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
285 - expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
286 - expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
287 - });
288 -
289 - it('flushes root with late deferred work in an animation callback if it wins', () => {
290 - // Schedule early animation
291 - ReactNoop.performAnimationWork(() => {
292 - ReactNoop.render(<span prop="1" />);
293 - });
294 - // Schedule late deferred
295 - ReactNoop.render(<span prop="2" />);
296 - expect(ReactNoop.getChildren()).toEqual([]);
297 -
298 - // Flushing animation work flushes everything on this root.
299 - ReactNoop.flushAnimationPri();
300 - expect(ReactNoop.getChildren()).toEqual([span('2')]);
301 - });
83 + it('schedules top-level updates in order of priority', () => {
84 + // Initial render.
85 + ReactNoop.render(<span prop={1} />);
86 + ReactNoop.flush();
87 + expect(ReactNoop.getChildren()).toEqual([span(1)]);
88
303 - it('flushes all roots with animation work in an animation callback if it wins', () => {
304 - // Schedule early animation
89 + ReactNoop.render(<span prop={5} />);
90 ReactNoop.performAnimationWork(() => {
306 - ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
307 - ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
91 + ReactNoop.render(<span prop={2} />);
92 + ReactNoop.render(<span prop={3} />);
93 + ReactNoop.render(<span prop={4} />);
94 });
309 - // Schedule late deferred
310 - ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
311 - ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
312 - expect(ReactNoop.getChildren('a')).toEqual([]);
313 - expect(ReactNoop.getChildren('b')).toEqual([]);
314 - expect(ReactNoop.getChildren('c')).toEqual([]);
95
316 - // Flushing animation work flushes all roots with animation work.
317 - ReactNoop.flushAnimationPri();
318 - expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
319 - expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
320 - expect(ReactNoop.getChildren('c')).toEqual([]);
321 -
322 - // Flushing deferred work flushes the root with only deferred work.
323 - ReactNoop.flushDeferredPri();
324 - expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
325 - expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
326 - expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
96 + // The low pri update should be flushed last, even though it was scheduled
97 + // before the animation updates.
98 + ReactNoop.flush();
99 + expect(ReactNoop.getChildren()).toEqual([span(5)]);
100 });
101
329 - it('splits deferred work on multiple roots', () => {
330 - // Schedule one root
331 - ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
332 - ReactNoop.flushDeferredPri(15 + 5);
333 - expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
102 + it('schedules top-level updates with same priority in order of insertion', () => {
103 + // Initial render.
104 + ReactNoop.render(<span prop={1} />);
105 + ReactNoop.flush();
106 + expect(ReactNoop.getChildren()).toEqual([span(1)]);
107
335 - // Schedule two roots
336 - ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
337 - ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
338 - // First scheduled one gets processed first
339 - ReactNoop.flushDeferredPri(15 + 5);
340 - expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
341 - expect(ReactNoop.getChildren('b')).toEqual([]);
342 - expect(ReactNoop.getChildren('c')).toEqual(null);
343 - // Then the second one gets processed
344 - ReactNoop.flushDeferredPri(15 + 5);
345 - expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
346 - expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
347 - expect(ReactNoop.getChildren('c')).toEqual(null);
108 + ReactNoop.render(<span prop={2} />);
109 + ReactNoop.render(<span prop={3} />);
110 + ReactNoop.render(<span prop={4} />);
111 + ReactNoop.render(<span prop={5} />);
112
349 - // Schedule three roots
350 - ReactNoop.renderToRootWithID(<span prop="a:3" />, 'a');
351 - ReactNoop.renderToRootWithID(<span prop="b:3" />, 'b');
352 - ReactNoop.renderToRootWithID(<span prop="c:3" />, 'c');
353 - // They get processed in the order they were scheduled
354 - ReactNoop.flushDeferredPri(15 + 5);
355 - expect(ReactNoop.getChildren('a')).toEqual([span('a:3')]);
356 - expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
357 - expect(ReactNoop.getChildren('c')).toEqual([]);
358 - ReactNoop.flushDeferredPri(15 + 5);
359 - expect(ReactNoop.getChildren('a')).toEqual([span('a:3')]);
360 - expect(ReactNoop.getChildren('b')).toEqual([span('b:3')]);
361 - expect(ReactNoop.getChildren('c')).toEqual([]);
362 - ReactNoop.flushDeferredPri(15 + 5);
363 - expect(ReactNoop.getChildren('a')).toEqual([span('a:3')]);
364 - expect(ReactNoop.getChildren('b')).toEqual([span('b:3')]);
365 - expect(ReactNoop.getChildren('c')).toEqual([span('c:3')]);
366 -
367 - // Schedule one root many times
368 - ReactNoop.renderToRootWithID(<span prop="a:4" />, 'a');
369 - ReactNoop.renderToRootWithID(<span prop="a:5" />, 'a');
370 - ReactNoop.renderToRootWithID(<span prop="a:6" />, 'a');
371 - ReactNoop.flushDeferredPri(15 + 5);
372 - expect(ReactNoop.getChildren('a')).toEqual([span('a:6')]);
113 + ReactNoop.flush();
114 + expect(ReactNoop.getChildren()).toEqual([span(5)]);
115 });
116
117 it('works on deferred roots in the order they were scheduled', () => {
@@ -402,54 +144,6 @@ describe('ReactIncrementalScheduling', () => {
144 expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
145 });
146
405 - it('handles interleaved deferred and animation work', () => {
406 - ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
407 - ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
408 - ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
409 - ReactNoop.flush();
410 - expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
411 - expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
412 - expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
413 -
414 - // Schedule both deferred and animation work
415 - ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
416 - ReactNoop.performAnimationWork(() => {
417 - ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
418 - ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
419 - });
420 - // We're flushing deferred work
421 - // Still, roots with animation work are handled first
422 - ReactNoop.flushDeferredPri(15);
423 - expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
424 - expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
425 - expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
426 - ReactNoop.flushDeferredPri(15);
427 - expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
428 - expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
429 - expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
430 -
431 - // More deferred and animation work just got scheduled!
432 - ReactNoop.renderToRootWithID(<span prop="c:3" />, 'c');
433 - ReactNoop.performAnimationWork(() => {
434 - ReactNoop.renderToRootWithID(<span prop="b:3" />, 'b');
435 - });
436 - // Animation is still handled first
437 - ReactNoop.flushDeferredPri(15);
438 - expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
439 - expect(ReactNoop.getChildren('b')).toEqual([span('b:3')]);
440 - expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
441 -
442 - // Finally we handle deferred root in the order it was scheduled
443 - ReactNoop.flushDeferredPri(15 + 5);
444 - expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
445 - expect(ReactNoop.getChildren('b')).toEqual([span('b:3')]);
446 - expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
447 - ReactNoop.flushDeferredPri(15 + 5);
448 - expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
449 - expect(ReactNoop.getChildren('b')).toEqual([span('b:3')]);
450 - expect(ReactNoop.getChildren('c')).toEqual([span('c:3')]);
451 - });
452 -
147 it('schedules sync updates when inside componentDidMount/Update', () => {
148 var instance;
149 var ops = [];
src/renderers/shared/shared/__tests__/ReactCompositeComponentState-test.js
+18 -8
@@ -169,16 +169,22 @@ describe('ReactCompositeComponent-state', () => {
169 // componentDidMount() called setState({color:'yellow'}), which is async.
170 // The update doesn't happen until the next flush.
171 ['componentDidMount-end', 'orange'],
172 + ['setState-sunrise', 'orange'],
173 + ['setState-orange', 'orange'],
174 ];
175
174 - // The setState callbacks in componentWillMount, and the initial callback
175 - // passed to ReactDOM.render, should be flushed right after component
176 - // did mount:
177 - expected.push(
178 - ['setState-sunrise', 'orange'], // 1
179 - ['setState-orange', 'orange'], // 2
180 - ['initial-callback', 'orange'], // 3
181 - ['shouldComponentUpdate-currentState', 'orange'],
176 + // In Fiber, the initial callback is not enqueued until after any work
177 + // scheduled by lifecycles has flushed (same semantics as a regular setState
178 + // callback outside of a batch). In Stack, the initial render is scheduled
179 + // inside of batchedUpdates, so the callback gets flushed right after
180 + // componentDidMount.
181 + // TODO: We should fix this, in both Stack and Fiber, so that the behavior
182 + // is consistent regardless of whether you're in a batch.
183 + if (!ReactDOMFeatureFlags.useFiber) {
184 + expected.push(['initial-callback', 'orange']);
185 + }
186 +
187 + expected.push(['shouldComponentUpdate-currentState', 'orange'],
188 ['shouldComponentUpdate-nextState', 'yellow'],
189 ['componentWillUpdate-currentState', 'orange'],
190 ['componentWillUpdate-nextState', 'yellow'],
@@ -188,6 +194,10 @@ describe('ReactCompositeComponent-state', () => {
194 ['setState-yellow', 'yellow'],
195 );
196
197 + if (ReactDOMFeatureFlags.useFiber) {
198 + expected.push(['initial-callback', 'yellow']);
199 + }
200 +
201 expected.push(
202 ['componentWillReceiveProps-start', 'yellow'],
203 // setState({color:'green'}) only enqueues a pending state.