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
d8ade83eb2019bffe19d71a9b1a03ad2e84b3150
8 files changed
+119
-465
scripts/fiber/tests-passing.txt
+5
-19
@@ -1181,27 +1181,13 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalReflection-test.js
1181
* finds no node before insertion and correct node before deletion
1182
1183
src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js
1184
-* schedules and flushes animation work
1185
-* schedules and flushes animation work for many roots
1186
-* flushes all scheduled animation work
1187
-* flushes all scheduled animation work for many roots
1184
* schedules and flushes deferred work
1189
-* schedules and flushes deferred work for many roots
1190
-* flushes scheduled deferred work fitting within deadline
1191
-* flushes scheduled deferred work fitting within deadline for many roots
1192
-* schedules more deferred work if it runs out of time
1193
-* schedules more deferred work if it runs out of time with many roots
1194
-* flushes late animation work in a deferred callback if it wins
1195
-* flushes late animation work in a deferred callback if it wins with many roots
1196
-* flushes late animation work in an animation callback if it wins
1197
-* flushes late animation work in an animation callback if it wins with many roots
1198
-* flushes all work in a deferred callback if it wins
1199
-* flushes all work in a deferred callback if it wins with many roots
1200
-* flushes root with late deferred work in an animation callback if it wins
1201
-* flushes all roots with animation work in an animation callback if it wins
1202
-* splits deferred work on multiple roots
1185
+* schedules and flushes animation work
1186
+* searches for work on other roots once the current root completes
1187
+* schedules an animation callback when there`s leftover animation work
1188
+* schedules top-level updates in order of priority
1189
+* schedules top-level updates with same priority in order of insertion
1190
* works on deferred roots in the order they were scheduled
1204
-* handles interleaved deferred and animation work
1191
* schedules sync updates when inside componentDidMount/Update
1192
* can opt-in to deferred/animation scheduling inside componentDidMount/Update
1193
* performs Task work even after time runs out
src/renderers/noop/ReactNoop.js
+4
-2
@@ -294,14 +294,16 @@ var ReactNoop = {
294
log(
295
' '.repeat(depth + 1) + '~',
296
firstUpdate && firstUpdate.partialState,
297
- firstUpdate.callback ? 'with callback' : ''
297
+ firstUpdate.callback ? 'with callback' : '',
298
+ '[' + firstUpdate.priorityLevel + ']'
299
);
300
var next;
301
while (next = firstUpdate.next) {
302
log(
303
' '.repeat(depth + 1) + '~',
304
next.partialState,
304
- next.callback ? 'with callback' : ''
305
+ next.callback ? 'with callback' : '',
306
+ '[' + firstUpdate.priorityLevel + ']'
307
);
308
}
309
}
src/renderers/shared/fiber/ReactFiberBeginWork.js
+7
-4
@@ -533,13 +533,16 @@ module.exports = function<T, P, I, TI, C, CX>(
533
pushTopLevelContextObject(root.context, false);
534
}
535
536
+ pushHostContainer(root.containerInfo);
537
+
538
if (updateQueue) {
537
- beginUpdateQueue(workInProgress, updateQueue, null, null, null, priorityLevel);
539
+ const prevState = workInProgress.memoizedState;
540
+ const state = beginUpdateQueue(workInProgress, updateQueue, null, prevState, null, priorityLevel);
541
+ const element = state.element;
542
+ reconcileChildren(current, workInProgress, element);
543
+ workInProgress.memoizedState = state;
544
}
545
540
- pushHostContainer(workInProgress.stateNode.containerInfo);
541
- reconcileChildren(current, workInProgress, pendingProps);
542
-
546
// A yield component is just a placeholder, we can just run through the
547
// next one immediately.
548
return workInProgress.child;
src/renderers/shared/fiber/ReactFiberReconciler.js
+5
-26
@@ -97,7 +97,7 @@ getContextForSubtree._injectFiber(function(fiber : Fiber) {
97
module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) : Reconciler<C, I, TI> {
98
99
var {
100
- scheduleWork,
100
+ scheduleSetState,
101
scheduleUpdateCallback,
102
performWithPriority,
103
batchedUpdates,
@@ -112,20 +112,11 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
112
const root = createFiberRoot(containerInfo, context);
113
const current = root.current;
114
115
- // TODO: Use the updateQueue and scheduleUpdate, instead of pendingProps.
116
- // TODO: This should not override the pendingWorkPriority if there is
117
- // higher priority work in the subtree.
118
-
119
- current.pendingProps = element;
120
- if (current.alternate) {
121
- current.alternate.pendingProps = element;
122
- }
115
+ scheduleSetState(current, { element });
116
if (callback) {
117
scheduleUpdateCallback(current, callback);
118
}
119
127
- scheduleWork(root);
128
-
120
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
121
ReactFiberInstrumentation.debugTool.onMountContainer(root);
122
}
@@ -143,19 +134,11 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
134
135
root.pendingContext = getContextForSubtree(parentComponent);
136
146
- // TODO: Use the updateQueue and scheduleUpdate, instead of pendingProps.
147
- // TODO: This should not override the pendingWorkPriority if there is
148
- // higher priority work in the subtree.
149
- current.pendingProps = element;
150
- if (current.alternate) {
151
- current.alternate.pendingProps = element;
152
- }
137
+ scheduleSetState(current, { element });
138
if (callback) {
139
scheduleUpdateCallback(current, callback);
140
}
141
157
- scheduleWork(root);
158
-
142
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
143
ReactFiberInstrumentation.debugTool.onUpdateContainer(root);
144
}
@@ -164,13 +147,9 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
147
unmountContainer(container : OpaqueNode) : void {
148
// TODO: If this is a nested container, this won't be the root.
149
const root : FiberRoot = (container.stateNode : any);
167
- // TODO: Use pending work/state instead of props.
168
- root.current.pendingProps = [];
169
- if (root.current.alternate) {
170
- root.current.alternate.pendingProps = [];
171
- }
150
+ const current = root.current;
151
173
- scheduleWork(root);
152
+ scheduleSetState(current, { element: [] });
153
154
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
155
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.