@samitouri / QOS-React-2 / commits / 5fcdebf712

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.