Move commit phase outside of performUnitOfWork
This gives us the ability to complete a tree without having to commit it within the same frame.
Andrew Clark committed
Dec 1, 2016 at 22:08 UTC
5fcdebf7120a1dde8463911e5fd8dcd341be5095
7 files changed
+165
-85
scripts/fiber/tests-passing.txt
+1
@@ -1161,6 +1161,7 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
1161
* preserves a previously rendered node when deprioritized
1162
* can reuse side-effects after being preempted
1163
* can reuse side-effects after being preempted, if shouldComponentUpdate is false
1164
+* can update a completed tree before it has a chance to commit
1165
* updates a child even though the old props is empty
1166
* can defer side-effects and resume them later on
1167
* can defer side-effects and reuse them later - complex
src/renderers/shared/fiber/ReactFiberReconciler.js
+6
@@ -143,6 +143,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
143
root.pendingContext = getContextForSubtree(parentComponent);
144
// TODO: Use pending work/state instead of props.
145
root.current.pendingProps = element;
146
+ if (root.current.alternate) {
147
+ root.current.alternate.pendingProps = element;
148
+ }
149
150
scheduleWork(root);
151
@@ -156,6 +159,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
159
const root : FiberRoot = (container.stateNode : any);
160
// TODO: Use pending work/state instead of props.
161
root.current.pendingProps = [];
162
+ if (root.current.alternate) {
163
+ root.current.alternate.pendingProps = [];
164
+ }
165
166
scheduleWork(root);
167
src/renderers/shared/fiber/ReactFiberScheduler.js
+94
-53
@@ -89,6 +89,8 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
89
let nextUnitOfWork : ?Fiber = null;
90
let nextPriorityLevel : PriorityLevel = NoWork;
91
92
+ let pendingCommit : Fiber | null = null;
93
+
94
// Linked list of roots with scheduled work on them.
95
let nextScheduledRoot : ?FiberRoot = null;
96
let lastScheduledRoot : ?FiberRoot = null;
@@ -115,6 +117,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
117
}
118
119
function findNextUnitOfWork() {
120
+ if (pendingCommit) {
121
+ return null;
122
+ }
123
+
124
// Clear out roots with no more work on them, or if they have uncaught errors
125
while (nextScheduledRoot && nextScheduledRoot.current.pendingWorkPriority === NoWork) {
126
// Unschedule this root.
@@ -160,6 +166,17 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
166
}
167
168
function commitAllWork(finishedWork : Fiber) {
169
+ pendingCommit = null;
170
+ const root : FiberRoot = (finishedWork.stateNode : any);
171
+ resetWorkPriority(finishedWork);
172
+ if (root.current === finishedWork) {
173
+ throw new Error(
174
+ 'Cannot commit the same tree as before. This is probably a bug ' +
175
+ 'related to the return field.'
176
+ );
177
+ }
178
+ root.current = finishedWork;
179
+
180
prepareForCommit();
181
182
// Commit all the side-effects within a tree.
@@ -293,11 +310,16 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
310
}
311
}
312
296
- // Flush any task work that was scheduled during this batch
297
- performTaskWork();
313
+ return null;
314
}
315
316
function resetWorkPriority(workInProgress : Fiber) {
317
+ if (workInProgress.pendingProps) {
318
+ // Don't reset the priority if the workInProgress has pending props. This
319
+ // could happen if a completed root is updated before it is able to commit.
320
+ return;
321
+ }
322
+
323
let newPriority = NoWork;
324
// progressedChild is going to be the child set with the highest priority.
325
// Either it is the same as child, or it just bailed out because it choose
@@ -324,20 +346,30 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
346
const current = workInProgress.alternate;
347
const next = completeWork(current, workInProgress);
348
327
- resetWorkPriority(workInProgress);
328
-
349
// The work is now done. We don't need this anymore. This flags
350
// to the system not to redo any work here.
351
workInProgress.pendingProps = null;
352
workInProgress.updateQueue = null;
353
354
+ const returnFiber = workInProgress.return;
355
+ const siblingFiber = workInProgress.sibling;
356
+ const isRoot = !returnFiber && !siblingFiber;
357
+
358
+ if (!isRoot) {
359
+ resetWorkPriority(workInProgress);
360
+ }
361
+
362
if (next) {
363
// If completing this work spawned new work, do that next. We'll come
364
// back here again.
365
return next;
366
}
367
340
- const returnFiber = workInProgress.return;
368
+ if (isRoot) {
369
+ // If we've reached the root, commit during the next unit of work.
370
+ pendingCommit = workInProgress;
371
+ return workInProgress;
372
+ }
373
374
if (returnFiber) {
375
// Append all the effects of the subtree and this fiber onto the effect
@@ -369,37 +401,15 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
401
}
402
}
403
372
- if (workInProgress.sibling) {
404
+ if (siblingFiber) {
405
// If there is more work to do in this returnFiber, do that next.
374
- return workInProgress.sibling;
406
+ return siblingFiber;
407
} else if (returnFiber) {
408
// If there's no more work in this returnFiber. Complete the returnFiber.
409
workInProgress = returnFiber;
410
continue;
411
} else {
380
- // If we're at the root, there's no more work to do. We can flush it.
381
- const root : FiberRoot = (workInProgress.stateNode : any);
382
- if (root.current === workInProgress) {
383
- throw new Error(
384
- 'Cannot commit the same tree as before. This is probably a bug ' +
385
- 'related to the return field.'
386
- );
387
- }
388
- root.current = workInProgress;
389
- // TODO: We can be smarter here and only look for more work in the
390
- // "next" scheduled work since we've already scanned passed. That
391
- // also ensures that work scheduled during reconciliation gets deferred.
392
- // const hasMoreWork = workInProgress.pendingWorkPriority !== NoWork;
393
- commitAllWork(workInProgress);
394
- const nextWork = findNextUnitOfWork();
395
- // if (!nextWork && hasMoreWork) {
396
- // TODO: This can happen when some deep work completes and we don't
397
- // know if this was the last one. We should be able to keep track of
398
- // the highest priority still in the tree for one pass. But if we
399
- // terminate an update we don't know.
400
- // throw new Error('FiberRoots should not have flagged more work if there is none.');
401
- // }
402
- return nextWork;
412
+ throw new Error('Should have already handled root.');
413
}
414
}
415
}
@@ -445,13 +455,17 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
455
}
456
457
function performDeferredWorkUnsafe(deadline) {
448
- if (!nextUnitOfWork) {
458
+ if (!nextUnitOfWork && !pendingCommit) {
459
nextUnitOfWork = findNextUnitOfWork();
460
}
451
- while (nextUnitOfWork) {
461
+ while (nextUnitOfWork || pendingCommit) {
462
if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
453
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
454
- if (!nextUnitOfWork) {
463
+ if (pendingCommit) {
464
+ nextUnitOfWork = commitAllWork(pendingCommit);
465
+ } else if (nextUnitOfWork) {
466
+ nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
467
+ }
468
+ if (!nextUnitOfWork && !pendingCommit) {
469
// Find more work. We might have time to complete some more.
470
nextUnitOfWork = findNextUnitOfWork();
471
}
@@ -468,12 +482,19 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
482
}
483
484
function performAnimationWorkUnsafe() {
471
- // Always start from the root
472
- nextUnitOfWork = findNextUnitOfWork();
473
- while (nextUnitOfWork &&
474
- nextPriorityLevel === AnimationPriority) {
475
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
476
- if (!nextUnitOfWork) {
485
+ if (!nextUnitOfWork && !pendingCommit) {
486
+ nextUnitOfWork = findNextUnitOfWork();
487
+ }
488
+ while (
489
+ pendingCommit ||
490
+ (nextUnitOfWork && nextPriorityLevel === AnimationPriority)
491
+ ) {
492
+ if (pendingCommit) {
493
+ nextUnitOfWork = commitAllWork(pendingCommit);
494
+ } else if (nextUnitOfWork) {
495
+ nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
496
+ }
497
+ if (!nextUnitOfWork && !pendingCommit) {
498
// Keep searching for animation work until there's no more left
499
nextUnitOfWork = findNextUnitOfWork();
500
}
@@ -489,12 +510,20 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
510
}
511
512
function performSynchronousWorkUnsafe() {
492
- nextUnitOfWork = findNextUnitOfWork();
493
- while (nextUnitOfWork &&
494
- nextPriorityLevel === SynchronousPriority) {
495
- nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
496
-
497
- if (!nextUnitOfWork) {
513
+ if (!nextUnitOfWork && !pendingCommit) {
514
+ nextUnitOfWork = findNextUnitOfWork();
515
+ }
516
+ while (
517
+ pendingCommit ||
518
+ (nextUnitOfWork && nextPriorityLevel === SynchronousPriority)
519
+ ) {
520
+ if (pendingCommit) {
521
+ nextUnitOfWork = commitAllWork(pendingCommit);
522
+ } else if (nextUnitOfWork) {
523
+ nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
524
+ }
525
+ if (!nextUnitOfWork && !pendingCommit) {
526
+ // Keep searching for sync work until there's no more left
527
nextUnitOfWork = findNextUnitOfWork();
528
}
529
}
@@ -514,13 +543,20 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
543
544
isPerformingTaskWork = true;
545
try {
517
- nextUnitOfWork = findNextUnitOfWork();
518
- while (nextUnitOfWork &&
519
- nextPriorityLevel === TaskPriority) {
520
- nextUnitOfWork =
521
- performUnitOfWork(nextUnitOfWork);
522
-
523
- if (!nextUnitOfWork) {
546
+ if (!nextUnitOfWork && !pendingCommit) {
547
+ nextUnitOfWork = findNextUnitOfWork();
548
+ }
549
+ while (
550
+ (pendingCommit && pendingCommit.pendingWorkPriority === TaskPriority) ||
551
+ (nextUnitOfWork && nextPriorityLevel === TaskPriority)
552
+ ) {
553
+ if (pendingCommit && pendingCommit.pendingWorkPriority === TaskPriority) {
554
+ nextUnitOfWork = commitAllWork(pendingCommit);
555
+ } else if (nextUnitOfWork) {
556
+ nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
557
+ }
558
+ if (!nextUnitOfWork && !pendingCommit) {
559
+ // Keep searching for sync work until there's no more left
560
nextUnitOfWork = findNextUnitOfWork();
561
}
562
}
@@ -597,6 +633,11 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
633
}
634
}
635
636
+ // Flush any task work that was scheduled during this batch
637
+ if (priorityLevel !== TaskPriority) {
638
+ performTaskWork();
639
+ }
640
+
641
// Throw the first uncaught error
642
if (!nextUnitOfWork && firstUncaughtError) {
643
let e = firstUncaughtError;
src/renderers/shared/fiber/__tests__/ReactIncremental-test.js
+4
-4
@@ -410,7 +410,7 @@ describe('ReactIncremental', () => {
410
// Make a quick update which will create a low pri tree on top of the
411
// already low pri tree.
412
ReactNoop.render(<Foo text="bar" />);
413
- ReactNoop.flushDeferredPri(15);
413
+ ReactNoop.flushDeferredPri(15 + 5);
414
415
expect(ops).toEqual(['Foo']);
416
@@ -491,7 +491,7 @@ describe('ReactIncremental', () => {
491
492
// Init
493
ReactNoop.render(<Foo text="foo" text2="foo" step={0} />);
494
- ReactNoop.flushDeferredPri(55 + 25 + 5);
494
+ ReactNoop.flushDeferredPri(55 + 25 + 5 + 5);
495
496
// We only finish the higher priority work. So the low pri content
497
// has not yet finished mounting.
@@ -520,7 +520,7 @@ describe('ReactIncremental', () => {
520
ops = [];
521
522
// The middle content is now pending rendering...
523
- ReactNoop.flushDeferredPri(30);
523
+ ReactNoop.flushDeferredPri(30 + 5);
524
expect(ops).toEqual(['Middle', 'Bar']);
525
526
ops = [];
@@ -600,7 +600,7 @@ describe('ReactIncremental', () => {
600
// Make a quick update which will schedule low priority work to
601
// update the middle content.
602
ReactNoop.render(<Foo text="bar" step={1} />);
603
- ReactNoop.flushDeferredPri(30);
603
+ ReactNoop.flushDeferredPri(30 + 5);
604
605
expect(ops).toEqual(['Foo', 'Bar']);
606
src/renderers/shared/fiber/__tests__/ReactIncrementalErrorHandling-test.js
+2
-2
@@ -88,7 +88,7 @@ describe('ReactIncrementalErrorHandling', () => {
88
expect(ReactNoop.getChildren()).toEqual([]);
89
90
ops.length = 0;
91
- ReactNoop.flushDeferredPri(30);
91
+ ReactNoop.flushDeferredPri(30 + 5 + 5 + 5);
92
expect(ops).toEqual([
93
'BrokenRender',
94
'ErrorBoundary unstable_handleError',
@@ -134,7 +134,7 @@ describe('ReactIncrementalErrorHandling', () => {
134
'BrokenRender',
135
]);
136
ops = [];
137
- ReactNoop.flushDeferredPri(25);
137
+ ReactNoop.flushDeferredPri(25 + 5 + 5);
138
expect(ops).toEqual([
139
'ErrorBoundary unstable_handleError',
140
'ErrorBoundary render error',
src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js
+23
-23
@@ -144,7 +144,7 @@ describe('ReactIncrementalScheduling', () => {
144
ReactNoop.flushDeferredPri(10);
145
expect(ReactNoop.getChildren()).toEqual([]);
146
147
- ReactNoop.flushDeferredPri(10);
147
+ ReactNoop.flushDeferredPri(10 + 5);
148
expect(ReactNoop.getChildren()).toEqual([span('2')]);
149
});
150
@@ -159,17 +159,17 @@ describe('ReactIncrementalScheduling', () => {
159
expect(ReactNoop.getChildren('b')).toEqual([]);
160
expect(ReactNoop.getChildren('c')).toEqual([]);
161
162
- ReactNoop.flushDeferredPri(15);
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);
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);
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')]);
@@ -329,19 +329,19 @@ describe('ReactIncrementalScheduling', () => {
329
it('splits deferred work on multiple roots', () => {
330
// Schedule one root
331
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
332
- ReactNoop.flushDeferredPri(15);
332
+ ReactNoop.flushDeferredPri(15 + 5);
333
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
334
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);
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);
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);
@@ -351,15 +351,15 @@ describe('ReactIncrementalScheduling', () => {
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);
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);
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);
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')]);
@@ -368,7 +368,7 @@ describe('ReactIncrementalScheduling', () => {
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);
371
+ ReactNoop.flushDeferredPri(15 + 5);
372
expect(ReactNoop.getChildren('a')).toEqual([span('a:6')]);
373
});
374
@@ -385,18 +385,18 @@ describe('ReactIncrementalScheduling', () => {
385
ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
386
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
387
// Ensure it starts in the order it was scheduled
388
- ReactNoop.flushDeferredPri(15);
388
+ ReactNoop.flushDeferredPri(15 + 5);
389
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
390
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
391
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
392
// Schedule last bit of work, it will get processed the last
393
ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
394
// Keep performing work in the order it was scheduled
395
- ReactNoop.flushDeferredPri(15);
395
+ ReactNoop.flushDeferredPri(15 + 5);
396
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
397
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
398
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
399
- ReactNoop.flushDeferredPri(15);
399
+ ReactNoop.flushDeferredPri(15 + 5);
400
expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
401
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
402
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
@@ -419,11 +419,11 @@ describe('ReactIncrementalScheduling', () => {
419
});
420
// We're flushing deferred work
421
// Still, roots with animation work are handled first
422
- ReactNoop.flushDeferredPri(15);
422
+ ReactNoop.flushDeferredPri(15 + 5);
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);
426
+ ReactNoop.flushDeferredPri(15 + 5);
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')]);
@@ -434,17 +434,17 @@ describe('ReactIncrementalScheduling', () => {
434
ReactNoop.renderToRootWithID(<span prop="b:3" />, 'b');
435
});
436
// Animation is still handled first
437
- ReactNoop.flushDeferredPri(15);
437
+ ReactNoop.flushDeferredPri(15 + 5);
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);
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);
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')]);
@@ -483,7 +483,7 @@ describe('ReactIncrementalScheduling', () => {
483
484
ReactNoop.render(<Foo />);
485
486
- ReactNoop.flushDeferredPri(20);
486
+ ReactNoop.flushDeferredPri(20 + 5);
487
expect(ops).toEqual([
488
'render: 0',
489
'componentDidMount (before setState): 0',
@@ -496,7 +496,7 @@ describe('ReactIncrementalScheduling', () => {
496
497
ops = [];
498
instance.setState({ tick: 2 });
499
- ReactNoop.flushDeferredPri(20);
499
+ ReactNoop.flushDeferredPri(20 + 5);
500
501
expect(ops).toEqual([
502
'render: 2',
@@ -545,7 +545,7 @@ describe('ReactIncrementalScheduling', () => {
545
546
ReactNoop.render(<Foo />);
547
548
- ReactNoop.flushDeferredPri(20);
548
+ ReactNoop.flushDeferredPri(20 + 5);
549
expect(ops).toEqual([
550
'render: 0',
551
'componentDidMount (before setState): 0',
@@ -566,7 +566,7 @@ describe('ReactIncrementalScheduling', () => {
566
567
ops = [];
568
instance.setState({ tick: 2 });
569
- ReactNoop.flushDeferredPri(20);
569
+ ReactNoop.flushDeferredPri(20 + 5);
570
571
expect(ops).toEqual([
572
'render: 2',
src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
+35
-3
@@ -480,6 +480,38 @@ describe('ReactIncrementalSideEffects', () => {
480
]);
481
});
482
483
+ it('can update a completed tree before it has a chance to commit', () => {
484
+ function Foo(props) {
485
+ return <span prop={props.step}/>;
486
+ }
487
+ ReactNoop.render(<Foo step={1} />);
488
+ // This should be just enough to complete the tree without committing it
489
+ ReactNoop.flushDeferredPri(20);
490
+ expect(ReactNoop.getChildren()).toEqual([]);
491
+ // To confirm, perform one more unit of work. The tree should now be flushed.
492
+ // (ReactNoop decrements the time remaining by 5 *before* returning it from
493
+ // the deadline, so to perform n units of work, you need to give it 5n + 5.
494
+ // TODO: This is confusing. Decerement it after.)
495
+ ReactNoop.flushDeferredPri(10);
496
+ expect(ReactNoop.getChildren()).toEqual([span(1)]);
497
+
498
+ ReactNoop.render(<Foo step={2} />);
499
+ // This should be just enough to complete the tree without committing it
500
+ ReactNoop.flushDeferredPri(20);
501
+ expect(ReactNoop.getChildren()).toEqual([span(1)]);
502
+ // This time, before we commit the tree, we update the root component with
503
+ // new props
504
+ ReactNoop.render(<Foo step={3} />);
505
+ // Now let's commit. We already had a commit that was pending, which will
506
+ // render 2.
507
+ ReactNoop.flushDeferredPri(10);
508
+ expect(ReactNoop.getChildren()).toEqual([span(2)]);
509
+ // If we flush the rest of the work, we should get another commit that
510
+ // renders 3. If it renders 2 again, that means an update was dropped.
511
+ ReactNoop.flush();
512
+ expect(ReactNoop.getChildren()).toEqual([span(3)]);
513
+ });
514
+
515
it('updates a child even though the old props is empty', () => {
516
function Foo(props) {
517
return (
@@ -611,7 +643,7 @@ describe('ReactIncrementalSideEffects', () => {
643
);
644
}
645
ReactNoop.render(<Foo tick={0} idx={0} />);
614
- ReactNoop.flushDeferredPri(65);
646
+ ReactNoop.flushDeferredPri(65 + 5);
647
expect(ReactNoop.getChildren()).toEqual([
648
div(
649
span(0),
@@ -770,7 +802,7 @@ describe('ReactIncrementalSideEffects', () => {
802
ops = [];
803
804
ReactNoop.render(<Foo tick={1} idx={1} />);
773
- ReactNoop.flushDeferredPri(70);
805
+ ReactNoop.flushDeferredPri(70 + 5);
806
expect(ReactNoop.getChildren()).toEqual([
807
div(
808
// Updated.
@@ -793,7 +825,7 @@ describe('ReactIncrementalSideEffects', () => {
825
// items. Including the set state since that is deprioritized.
826
// TODO: The cycles it takes to do this could be lowered with further
827
// optimizations.
796
- ReactNoop.flushDeferredPri(60);
828
+ ReactNoop.flushDeferredPri(60 + 5);
829
expect(ReactNoop.getChildren()).toEqual([
830
div(
831
// Updated.