mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Move commit phase outside of performUnitOfWork
This gives us the ability to complete a tree without having to commit it within the same frame.
This commit is contained in:
committed by
Andrew Clark
parent
9510ecfc5e
commit
5fcdebf712
@@ -1161,6 +1161,7 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalSideEffects-test.js
|
||||
* preserves a previously rendered node when deprioritized
|
||||
* can reuse side-effects after being preempted
|
||||
* can reuse side-effects after being preempted, if shouldComponentUpdate is false
|
||||
* can update a completed tree before it has a chance to commit
|
||||
* updates a child even though the old props is empty
|
||||
* can defer side-effects and resume them later on
|
||||
* can defer side-effects and reuse them later - complex
|
||||
|
||||
@@ -143,6 +143,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
|
||||
root.pendingContext = getContextForSubtree(parentComponent);
|
||||
// TODO: Use pending work/state instead of props.
|
||||
root.current.pendingProps = element;
|
||||
if (root.current.alternate) {
|
||||
root.current.alternate.pendingProps = element;
|
||||
}
|
||||
|
||||
scheduleWork(root);
|
||||
|
||||
@@ -156,6 +159,9 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) :
|
||||
const root : FiberRoot = (container.stateNode : any);
|
||||
// TODO: Use pending work/state instead of props.
|
||||
root.current.pendingProps = [];
|
||||
if (root.current.alternate) {
|
||||
root.current.alternate.pendingProps = [];
|
||||
}
|
||||
|
||||
scheduleWork(root);
|
||||
|
||||
|
||||
@@ -89,6 +89,8 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
let nextUnitOfWork : ?Fiber = null;
|
||||
let nextPriorityLevel : PriorityLevel = NoWork;
|
||||
|
||||
let pendingCommit : Fiber | null = null;
|
||||
|
||||
// Linked list of roots with scheduled work on them.
|
||||
let nextScheduledRoot : ?FiberRoot = null;
|
||||
let lastScheduledRoot : ?FiberRoot = null;
|
||||
@@ -115,6 +117,10 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
}
|
||||
|
||||
function findNextUnitOfWork() {
|
||||
if (pendingCommit) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Clear out roots with no more work on them, or if they have uncaught errors
|
||||
while (nextScheduledRoot && nextScheduledRoot.current.pendingWorkPriority === NoWork) {
|
||||
// Unschedule this root.
|
||||
@@ -160,6 +166,17 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
}
|
||||
|
||||
function commitAllWork(finishedWork : Fiber) {
|
||||
pendingCommit = null;
|
||||
const root : FiberRoot = (finishedWork.stateNode : any);
|
||||
resetWorkPriority(finishedWork);
|
||||
if (root.current === finishedWork) {
|
||||
throw new Error(
|
||||
'Cannot commit the same tree as before. This is probably a bug ' +
|
||||
'related to the return field.'
|
||||
);
|
||||
}
|
||||
root.current = finishedWork;
|
||||
|
||||
prepareForCommit();
|
||||
|
||||
// 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>) {
|
||||
}
|
||||
}
|
||||
|
||||
// Flush any task work that was scheduled during this batch
|
||||
performTaskWork();
|
||||
return null;
|
||||
}
|
||||
|
||||
function resetWorkPriority(workInProgress : Fiber) {
|
||||
if (workInProgress.pendingProps) {
|
||||
// Don't reset the priority if the workInProgress has pending props. This
|
||||
// could happen if a completed root is updated before it is able to commit.
|
||||
return;
|
||||
}
|
||||
|
||||
let newPriority = NoWork;
|
||||
// progressedChild is going to be the child set with the highest priority.
|
||||
// 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>) {
|
||||
const current = workInProgress.alternate;
|
||||
const next = completeWork(current, workInProgress);
|
||||
|
||||
resetWorkPriority(workInProgress);
|
||||
|
||||
// The work is now done. We don't need this anymore. This flags
|
||||
// to the system not to redo any work here.
|
||||
workInProgress.pendingProps = null;
|
||||
workInProgress.updateQueue = null;
|
||||
|
||||
const returnFiber = workInProgress.return;
|
||||
const siblingFiber = workInProgress.sibling;
|
||||
const isRoot = !returnFiber && !siblingFiber;
|
||||
|
||||
if (!isRoot) {
|
||||
resetWorkPriority(workInProgress);
|
||||
}
|
||||
|
||||
if (next) {
|
||||
// If completing this work spawned new work, do that next. We'll come
|
||||
// back here again.
|
||||
return next;
|
||||
}
|
||||
|
||||
const returnFiber = workInProgress.return;
|
||||
if (isRoot) {
|
||||
// If we've reached the root, commit during the next unit of work.
|
||||
pendingCommit = workInProgress;
|
||||
return workInProgress;
|
||||
}
|
||||
|
||||
if (returnFiber) {
|
||||
// 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>) {
|
||||
}
|
||||
}
|
||||
|
||||
if (workInProgress.sibling) {
|
||||
if (siblingFiber) {
|
||||
// If there is more work to do in this returnFiber, do that next.
|
||||
return workInProgress.sibling;
|
||||
return siblingFiber;
|
||||
} else if (returnFiber) {
|
||||
// If there's no more work in this returnFiber. Complete the returnFiber.
|
||||
workInProgress = returnFiber;
|
||||
continue;
|
||||
} else {
|
||||
// If we're at the root, there's no more work to do. We can flush it.
|
||||
const root : FiberRoot = (workInProgress.stateNode : any);
|
||||
if (root.current === workInProgress) {
|
||||
throw new Error(
|
||||
'Cannot commit the same tree as before. This is probably a bug ' +
|
||||
'related to the return field.'
|
||||
);
|
||||
}
|
||||
root.current = workInProgress;
|
||||
// TODO: We can be smarter here and only look for more work in the
|
||||
// "next" scheduled work since we've already scanned passed. That
|
||||
// also ensures that work scheduled during reconciliation gets deferred.
|
||||
// const hasMoreWork = workInProgress.pendingWorkPriority !== NoWork;
|
||||
commitAllWork(workInProgress);
|
||||
const nextWork = findNextUnitOfWork();
|
||||
// if (!nextWork && hasMoreWork) {
|
||||
// TODO: This can happen when some deep work completes and we don't
|
||||
// know if this was the last one. We should be able to keep track of
|
||||
// the highest priority still in the tree for one pass. But if we
|
||||
// terminate an update we don't know.
|
||||
// throw new Error('FiberRoots should not have flagged more work if there is none.');
|
||||
// }
|
||||
return nextWork;
|
||||
throw new Error('Should have already handled root.');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -445,13 +455,17 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
}
|
||||
|
||||
function performDeferredWorkUnsafe(deadline) {
|
||||
if (!nextUnitOfWork) {
|
||||
if (!nextUnitOfWork && !pendingCommit) {
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
}
|
||||
while (nextUnitOfWork) {
|
||||
while (nextUnitOfWork || pendingCommit) {
|
||||
if (deadline.timeRemaining() > timeHeuristicForUnitOfWork) {
|
||||
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
|
||||
if (!nextUnitOfWork) {
|
||||
if (pendingCommit) {
|
||||
nextUnitOfWork = commitAllWork(pendingCommit);
|
||||
} else if (nextUnitOfWork) {
|
||||
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
|
||||
}
|
||||
if (!nextUnitOfWork && !pendingCommit) {
|
||||
// Find more work. We might have time to complete some more.
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
}
|
||||
@@ -468,12 +482,19 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
}
|
||||
|
||||
function performAnimationWorkUnsafe() {
|
||||
// Always start from the root
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
while (nextUnitOfWork &&
|
||||
nextPriorityLevel === AnimationPriority) {
|
||||
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
|
||||
if (!nextUnitOfWork) {
|
||||
if (!nextUnitOfWork && !pendingCommit) {
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
}
|
||||
while (
|
||||
pendingCommit ||
|
||||
(nextUnitOfWork && nextPriorityLevel === AnimationPriority)
|
||||
) {
|
||||
if (pendingCommit) {
|
||||
nextUnitOfWork = commitAllWork(pendingCommit);
|
||||
} else if (nextUnitOfWork) {
|
||||
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
|
||||
}
|
||||
if (!nextUnitOfWork && !pendingCommit) {
|
||||
// Keep searching for animation work until there's no more left
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
}
|
||||
@@ -489,12 +510,20 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
}
|
||||
|
||||
function performSynchronousWorkUnsafe() {
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
while (nextUnitOfWork &&
|
||||
nextPriorityLevel === SynchronousPriority) {
|
||||
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
|
||||
|
||||
if (!nextUnitOfWork) {
|
||||
if (!nextUnitOfWork && !pendingCommit) {
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
}
|
||||
while (
|
||||
pendingCommit ||
|
||||
(nextUnitOfWork && nextPriorityLevel === SynchronousPriority)
|
||||
) {
|
||||
if (pendingCommit) {
|
||||
nextUnitOfWork = commitAllWork(pendingCommit);
|
||||
} else if (nextUnitOfWork) {
|
||||
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
|
||||
}
|
||||
if (!nextUnitOfWork && !pendingCommit) {
|
||||
// Keep searching for sync work until there's no more left
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
}
|
||||
}
|
||||
@@ -514,13 +543,20 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
|
||||
isPerformingTaskWork = true;
|
||||
try {
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
while (nextUnitOfWork &&
|
||||
nextPriorityLevel === TaskPriority) {
|
||||
nextUnitOfWork =
|
||||
performUnitOfWork(nextUnitOfWork);
|
||||
|
||||
if (!nextUnitOfWork) {
|
||||
if (!nextUnitOfWork && !pendingCommit) {
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
}
|
||||
while (
|
||||
(pendingCommit && pendingCommit.pendingWorkPriority === TaskPriority) ||
|
||||
(nextUnitOfWork && nextPriorityLevel === TaskPriority)
|
||||
) {
|
||||
if (pendingCommit && pendingCommit.pendingWorkPriority === TaskPriority) {
|
||||
nextUnitOfWork = commitAllWork(pendingCommit);
|
||||
} else if (nextUnitOfWork) {
|
||||
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
|
||||
}
|
||||
if (!nextUnitOfWork && !pendingCommit) {
|
||||
// Keep searching for sync work until there's no more left
|
||||
nextUnitOfWork = findNextUnitOfWork();
|
||||
}
|
||||
}
|
||||
@@ -597,6 +633,11 @@ module.exports = function<T, P, I, TI, C>(config : HostConfig<T, P, I, TI, C>) {
|
||||
}
|
||||
}
|
||||
|
||||
// Flush any task work that was scheduled during this batch
|
||||
if (priorityLevel !== TaskPriority) {
|
||||
performTaskWork();
|
||||
}
|
||||
|
||||
// Throw the first uncaught error
|
||||
if (!nextUnitOfWork && firstUncaughtError) {
|
||||
let e = firstUncaughtError;
|
||||
|
||||
@@ -410,7 +410,7 @@ describe('ReactIncremental', () => {
|
||||
// Make a quick update which will create a low pri tree on top of the
|
||||
// already low pri tree.
|
||||
ReactNoop.render(<Foo text="bar" />);
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
|
||||
expect(ops).toEqual(['Foo']);
|
||||
|
||||
@@ -491,7 +491,7 @@ describe('ReactIncremental', () => {
|
||||
|
||||
// Init
|
||||
ReactNoop.render(<Foo text="foo" text2="foo" step={0} />);
|
||||
ReactNoop.flushDeferredPri(55 + 25 + 5);
|
||||
ReactNoop.flushDeferredPri(55 + 25 + 5 + 5);
|
||||
|
||||
// We only finish the higher priority work. So the low pri content
|
||||
// has not yet finished mounting.
|
||||
@@ -520,7 +520,7 @@ describe('ReactIncremental', () => {
|
||||
ops = [];
|
||||
|
||||
// The middle content is now pending rendering...
|
||||
ReactNoop.flushDeferredPri(30);
|
||||
ReactNoop.flushDeferredPri(30 + 5);
|
||||
expect(ops).toEqual(['Middle', 'Bar']);
|
||||
|
||||
ops = [];
|
||||
@@ -600,7 +600,7 @@ describe('ReactIncremental', () => {
|
||||
// Make a quick update which will schedule low priority work to
|
||||
// update the middle content.
|
||||
ReactNoop.render(<Foo text="bar" step={1} />);
|
||||
ReactNoop.flushDeferredPri(30);
|
||||
ReactNoop.flushDeferredPri(30 + 5);
|
||||
|
||||
expect(ops).toEqual(['Foo', 'Bar']);
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
|
||||
ops.length = 0;
|
||||
ReactNoop.flushDeferredPri(30);
|
||||
ReactNoop.flushDeferredPri(30 + 5 + 5 + 5);
|
||||
expect(ops).toEqual([
|
||||
'BrokenRender',
|
||||
'ErrorBoundary unstable_handleError',
|
||||
@@ -134,7 +134,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
'BrokenRender',
|
||||
]);
|
||||
ops = [];
|
||||
ReactNoop.flushDeferredPri(25);
|
||||
ReactNoop.flushDeferredPri(25 + 5 + 5);
|
||||
expect(ops).toEqual([
|
||||
'ErrorBoundary unstable_handleError',
|
||||
'ErrorBoundary render error',
|
||||
|
||||
@@ -144,7 +144,7 @@ describe('ReactIncrementalScheduling', () => {
|
||||
ReactNoop.flushDeferredPri(10);
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
|
||||
ReactNoop.flushDeferredPri(10);
|
||||
ReactNoop.flushDeferredPri(10 + 5);
|
||||
expect(ReactNoop.getChildren()).toEqual([span('2')]);
|
||||
});
|
||||
|
||||
@@ -159,17 +159,17 @@ describe('ReactIncrementalScheduling', () => {
|
||||
expect(ReactNoop.getChildren('b')).toEqual([]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([]);
|
||||
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([]);
|
||||
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([]);
|
||||
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
|
||||
@@ -329,19 +329,19 @@ describe('ReactIncrementalScheduling', () => {
|
||||
it('splits deferred work on multiple roots', () => {
|
||||
// Schedule one root
|
||||
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
|
||||
|
||||
// Schedule two roots
|
||||
ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
|
||||
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
|
||||
// First scheduled one gets processed first
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual(null);
|
||||
// Then the second one gets processed
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual(null);
|
||||
@@ -351,15 +351,15 @@ describe('ReactIncrementalScheduling', () => {
|
||||
ReactNoop.renderToRootWithID(<span prop="b:3" />, 'b');
|
||||
ReactNoop.renderToRootWithID(<span prop="c:3" />, 'c');
|
||||
// They get processed in the order they were scheduled
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:3')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([]);
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:3')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:3')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([]);
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:3')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:3')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:3')]);
|
||||
@@ -368,7 +368,7 @@ describe('ReactIncrementalScheduling', () => {
|
||||
ReactNoop.renderToRootWithID(<span prop="a:4" />, 'a');
|
||||
ReactNoop.renderToRootWithID(<span prop="a:5" />, 'a');
|
||||
ReactNoop.renderToRootWithID(<span prop="a:6" />, 'a');
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:6')]);
|
||||
});
|
||||
|
||||
@@ -385,18 +385,18 @@ describe('ReactIncrementalScheduling', () => {
|
||||
ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
|
||||
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
|
||||
// Ensure it starts in the order it was scheduled
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
|
||||
// Schedule last bit of work, it will get processed the last
|
||||
ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
|
||||
// Keep performing work in the order it was scheduled
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
|
||||
@@ -419,11 +419,11 @@ describe('ReactIncrementalScheduling', () => {
|
||||
});
|
||||
// We're flushing deferred work
|
||||
// Still, roots with animation work are handled first
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
|
||||
@@ -434,17 +434,17 @@ describe('ReactIncrementalScheduling', () => {
|
||||
ReactNoop.renderToRootWithID(<span prop="b:3" />, 'b');
|
||||
});
|
||||
// Animation is still handled first
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:3')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
|
||||
|
||||
// Finally we handle deferred root in the order it was scheduled
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:3')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
|
||||
ReactNoop.flushDeferredPri(15);
|
||||
ReactNoop.flushDeferredPri(15 + 5);
|
||||
expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
|
||||
expect(ReactNoop.getChildren('b')).toEqual([span('b:3')]);
|
||||
expect(ReactNoop.getChildren('c')).toEqual([span('c:3')]);
|
||||
@@ -483,7 +483,7 @@ describe('ReactIncrementalScheduling', () => {
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
|
||||
ReactNoop.flushDeferredPri(20);
|
||||
ReactNoop.flushDeferredPri(20 + 5);
|
||||
expect(ops).toEqual([
|
||||
'render: 0',
|
||||
'componentDidMount (before setState): 0',
|
||||
@@ -496,7 +496,7 @@ describe('ReactIncrementalScheduling', () => {
|
||||
|
||||
ops = [];
|
||||
instance.setState({ tick: 2 });
|
||||
ReactNoop.flushDeferredPri(20);
|
||||
ReactNoop.flushDeferredPri(20 + 5);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'render: 2',
|
||||
@@ -545,7 +545,7 @@ describe('ReactIncrementalScheduling', () => {
|
||||
|
||||
ReactNoop.render(<Foo />);
|
||||
|
||||
ReactNoop.flushDeferredPri(20);
|
||||
ReactNoop.flushDeferredPri(20 + 5);
|
||||
expect(ops).toEqual([
|
||||
'render: 0',
|
||||
'componentDidMount (before setState): 0',
|
||||
@@ -566,7 +566,7 @@ describe('ReactIncrementalScheduling', () => {
|
||||
|
||||
ops = [];
|
||||
instance.setState({ tick: 2 });
|
||||
ReactNoop.flushDeferredPri(20);
|
||||
ReactNoop.flushDeferredPri(20 + 5);
|
||||
|
||||
expect(ops).toEqual([
|
||||
'render: 2',
|
||||
|
||||
@@ -480,6 +480,38 @@ describe('ReactIncrementalSideEffects', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('can update a completed tree before it has a chance to commit', () => {
|
||||
function Foo(props) {
|
||||
return <span prop={props.step}/>;
|
||||
}
|
||||
ReactNoop.render(<Foo step={1} />);
|
||||
// This should be just enough to complete the tree without committing it
|
||||
ReactNoop.flushDeferredPri(20);
|
||||
expect(ReactNoop.getChildren()).toEqual([]);
|
||||
// To confirm, perform one more unit of work. The tree should now be flushed.
|
||||
// (ReactNoop decrements the time remaining by 5 *before* returning it from
|
||||
// the deadline, so to perform n units of work, you need to give it 5n + 5.
|
||||
// TODO: This is confusing. Decerement it after.)
|
||||
ReactNoop.flushDeferredPri(10);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(1)]);
|
||||
|
||||
ReactNoop.render(<Foo step={2} />);
|
||||
// This should be just enough to complete the tree without committing it
|
||||
ReactNoop.flushDeferredPri(20);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(1)]);
|
||||
// This time, before we commit the tree, we update the root component with
|
||||
// new props
|
||||
ReactNoop.render(<Foo step={3} />);
|
||||
// Now let's commit. We already had a commit that was pending, which will
|
||||
// render 2.
|
||||
ReactNoop.flushDeferredPri(10);
|
||||
expect(ReactNoop.getChildren()).toEqual([span(2)]);
|
||||
// If we flush the rest of the work, we should get another commit that
|
||||
// renders 3. If it renders 2 again, that means an update was dropped.
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren()).toEqual([span(3)]);
|
||||
});
|
||||
|
||||
it('updates a child even though the old props is empty', () => {
|
||||
function Foo(props) {
|
||||
return (
|
||||
@@ -611,7 +643,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||
);
|
||||
}
|
||||
ReactNoop.render(<Foo tick={0} idx={0} />);
|
||||
ReactNoop.flushDeferredPri(65);
|
||||
ReactNoop.flushDeferredPri(65 + 5);
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
div(
|
||||
span(0),
|
||||
@@ -770,7 +802,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||
ops = [];
|
||||
|
||||
ReactNoop.render(<Foo tick={1} idx={1} />);
|
||||
ReactNoop.flushDeferredPri(70);
|
||||
ReactNoop.flushDeferredPri(70 + 5);
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
div(
|
||||
// Updated.
|
||||
@@ -793,7 +825,7 @@ describe('ReactIncrementalSideEffects', () => {
|
||||
// items. Including the set state since that is deprioritized.
|
||||
// TODO: The cycles it takes to do this could be lowered with further
|
||||
// optimizations.
|
||||
ReactNoop.flushDeferredPri(60);
|
||||
ReactNoop.flushDeferredPri(60 + 5);
|
||||
expect(ReactNoop.getChildren()).toEqual([
|
||||
div(
|
||||
// Updated.
|
||||
|
||||
Reference in New Issue
Block a user