Use an UpdateQueue for top-level updates

...rather than mutate the pendingProps directly, which throws away any
previously scheduled work.
This commit is contained in:
Andrew Clark
2016-12-15 13:58:22 -08:00
parent ab19dd0292
commit c6ccc19095
8 changed files with 129 additions and 475 deletions

View File

@@ -1190,27 +1190,13 @@ src/renderers/shared/fiber/__tests__/ReactIncrementalReflection-test.js
* finds no node before insertion and correct node before deletion
src/renderers/shared/fiber/__tests__/ReactIncrementalScheduling-test.js
* schedules and flushes animation work
* schedules and flushes animation work for many roots
* flushes all scheduled animation work
* flushes all scheduled animation work for many roots
* schedules and flushes deferred work
* schedules and flushes deferred work for many roots
* flushes scheduled deferred work fitting within deadline
* flushes scheduled deferred work fitting within deadline for many roots
* schedules more deferred work if it runs out of time
* schedules more deferred work if it runs out of time with many roots
* flushes late animation work in a deferred callback if it wins
* flushes late animation work in a deferred callback if it wins with many roots
* flushes late animation work in an animation callback if it wins
* flushes late animation work in an animation callback if it wins with many roots
* flushes all work in a deferred callback if it wins
* flushes all work in a deferred callback if it wins with many roots
* flushes root with late deferred work in an animation callback if it wins
* flushes all roots with animation work in an animation callback if it wins
* splits deferred work on multiple roots
* schedules and flushes animation work
* searches for work on other roots once the current root completes
* schedules an animation callback when there`s leftover animation work
* schedules top-level updates in order of priority
* schedules top-level updates with same priority in order of insertion
* works on deferred roots in the order they were scheduled
* handles interleaved deferred and animation work
* schedules sync updates when inside componentDidMount/Update
* can opt-in to deferred/animation scheduling inside componentDidMount/Update
* performs Task work even after time runs out

View File

@@ -304,14 +304,16 @@ var ReactNoop = {
log(
' '.repeat(depth + 1) + '~',
firstUpdate && firstUpdate.partialState,
firstUpdate.callback ? 'with callback' : ''
firstUpdate.callback ? 'with callback' : '',
'[' + firstUpdate.priorityLevel + ']'
);
var next;
while (next = firstUpdate.next) {
log(
' '.repeat(depth + 1) + '~',
next.partialState,
next.callback ? 'with callback' : ''
next.callback ? 'with callback' : '',
'[' + firstUpdate.priorityLevel + ']'
);
}
}

View File

@@ -542,12 +542,15 @@ module.exports = function<T, P, I, TI, C, CX>(
pushTopLevelContextObject(root.context, false);
}
if (updateQueue) {
beginUpdateQueue(workInProgress, updateQueue, null, null, null, priorityLevel);
}
pushHostContainer(root.containerInfo);
pushHostContainer(workInProgress.stateNode.containerInfo);
reconcileChildren(current, workInProgress, pendingProps);
if (updateQueue) {
const prevState = workInProgress.memoizedState;
const state = beginUpdateQueue(workInProgress, updateQueue, null, prevState, null, priorityLevel);
const element = state.element;
reconcileChildren(current, workInProgress, element);
workInProgress.memoizedState = state;
}
// A yield component is just a placeholder, we can just run through the
// next one immediately.

View File

@@ -98,7 +98,7 @@ getContextForSubtree._injectFiber(function(fiber : Fiber) {
module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C, CX>) : Reconciler<C, I, TI> {
var {
scheduleWork,
scheduleSetState,
scheduleUpdateCallback,
performWithPriority,
batchedUpdates,
@@ -113,20 +113,11 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
const root = createFiberRoot(containerInfo, context);
const current = root.current;
// TODO: Use the updateQueue and scheduleUpdate, instead of pendingProps.
// TODO: This should not override the pendingWorkPriority if there is
// higher priority work in the subtree.
current.pendingProps = element;
if (current.alternate) {
current.alternate.pendingProps = element;
}
scheduleSetState(current, { element });
if (callback) {
scheduleUpdateCallback(current, callback);
}
scheduleWork(root);
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
ReactFiberInstrumentation.debugTool.onMountContainer(root);
}
@@ -144,19 +135,11 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
root.pendingContext = getContextForSubtree(parentComponent);
// TODO: Use the updateQueue and scheduleUpdate, instead of pendingProps.
// TODO: This should not override the pendingWorkPriority if there is
// higher priority work in the subtree.
current.pendingProps = element;
if (current.alternate) {
current.alternate.pendingProps = element;
}
scheduleSetState(current, { element });
if (callback) {
scheduleUpdateCallback(current, callback);
}
scheduleWork(root);
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
ReactFiberInstrumentation.debugTool.onUpdateContainer(root);
}
@@ -165,13 +148,9 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
unmountContainer(container : OpaqueNode) : void {
// TODO: If this is a nested container, this won't be the root.
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 = [];
}
const current = root.current;
scheduleWork(root);
scheduleSetState(current, { element: [] });
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
ReactFiberInstrumentation.debugTool.onUnmountContainer(root);

View File

@@ -944,28 +944,10 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
}
}
function scheduleWork(root : FiberRoot) {
let priorityLevel = priorityContext;
// If we're in a batch, switch to task priority
if (priorityLevel === SynchronousPriority && isPerformingWork) {
priorityLevel = TaskPriority;
}
scheduleWorkAtPriority(root, priorityLevel);
}
function scheduleWorkAtPriority(root : FiberRoot, priorityLevel : PriorityLevel) {
// Set the priority on the root, without deprioritizing
if (root.current.pendingWorkPriority === NoWork ||
priorityLevel <= root.current.pendingWorkPriority) {
root.current.pendingWorkPriority = priorityLevel;
}
if (root.current.alternate) {
if (root.current.alternate.pendingWorkPriority === NoWork ||
priorityLevel <= root.current.alternate.pendingWorkPriority) {
root.current.alternate.pendingWorkPriority = priorityLevel;
}
function scheduleRoot(root : FiberRoot) {
const priorityLevel = root.current.pendingWorkPriority;
if (priorityLevel === NoWork) {
return;
}
if (!root.isScheduled) {
@@ -987,30 +969,6 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
// priority work somewhere earlier than before.
nextUnitOfWork = null;
}
// Depending on the priority level, either perform work now or schedule
// a callback to perform work later.
switch (priorityLevel) {
case SynchronousPriority:
// Perform work immediately
performWork(SynchronousPriority);
return;
case TaskPriority:
// If we're already performing work, Task work will be flushed before
// exiting the current batch. So we can skip it here.
if (!isPerformingWork) {
performWork(TaskPriority);
}
return;
case AnimationPriority:
scheduleAnimationCallback(performAnimationWork);
return;
case HighPriority:
case LowPriority:
case OffscreenPriority:
scheduleDeferredCallback(performDeferredWork);
return;
}
}
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
if (!node.return) {
if (node.tag === HostRoot) {
const root : FiberRoot = (node.stateNode : any);
scheduleWorkAtPriority(root, priorityLevel);
scheduleRoot(root, priorityLevel);
// Depending on the priority level, either perform work now or
// schedule a callback to perform work later.
switch (priorityLevel) {
case SynchronousPriority:
// Perform work immediately
performWork(SynchronousPriority);
return;
case TaskPriority:
// If we're already performing work, Task work will be flushed before
// exiting the current batch. So we can skip it here.
if (!isPerformingWork) {
performWork(TaskPriority);
}
return;
case AnimationPriority:
scheduleAnimationCallback(performAnimationWork);
return;
case HighPriority:
case LowPriority:
case OffscreenPriority:
scheduleDeferredCallback(performDeferredWork);
return;
}
} else {
// TODO: Warn about setting state on an unmounted component.
return;
@@ -1124,7 +1105,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
}
return {
scheduleWork: scheduleWork,
scheduleSetState: scheduleSetState,
scheduleUpdateCallback: scheduleUpdateCallback,
performWithPriority: performWithPriority,
batchedUpdates: batchedUpdates,

View File

@@ -267,7 +267,6 @@ describe('ReactIncremental', () => {
ReactNoop.performAnimationWork(() => {
ReactNoop.render(<Foo text="foo" />);
});
ReactNoop.render(<Foo text="bar" />);
ReactNoop.flushAnimationPri();
expect(ops).toEqual(['Foo', 'Bar', 'Bar']);

View File

@@ -25,6 +25,14 @@ describe('ReactIncrementalScheduling', () => {
return { type: 'span', children: [], prop };
}
it('schedules and flushes deferred work', () => {
ReactNoop.render(<span prop="1" />);
expect(ReactNoop.getChildren()).toEqual([]);
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren()).toEqual([span('1')]);
});
it('schedules and flushes animation work', () => {
ReactNoop.performAnimationWork(() => {
ReactNoop.render(<span prop="1" />);
@@ -35,341 +43,75 @@ describe('ReactIncrementalScheduling', () => {
expect(ReactNoop.getChildren()).toEqual([span('1')]);
});
it('schedules and flushes animation work for many roots', () => {
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
});
expect(ReactNoop.getChildren('a')).toEqual([]);
expect(ReactNoop.getChildren('b')).toEqual([]);
expect(ReactNoop.getChildren('c')).toEqual([]);
it('searches for work on other roots once the current root completes', () => {
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
ReactNoop.flush();
ReactNoop.flushAnimationPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
});
it('flushes all scheduled animation work', () => {
ReactNoop.performAnimationWork(() => {
ReactNoop.render(<span prop="1" />);
});
ReactNoop.performAnimationWork(() => {
ReactNoop.render(<span prop="2" />);
});
expect(ReactNoop.getChildren()).toEqual([]);
it('schedules an animation callback when there`\s leftover animation work', () => {
class Foo extends React.Component {
state = { step: 0 };
componentDidMount() {
ReactNoop.performAnimationWork(() => {
this.setState({ step: 2 });
});
this.setState({ step: 1 });
}
render() {
return <span prop={this.state.step} />;
}
}
ReactNoop.render(<Foo />);
// Flush just enough work to mount the component, but not enough to flush
// the animation update.
ReactNoop.flushDeferredPri(25);
expect(ReactNoop.getChildren()).toEqual([span(1)]);
// There's more animation work. A callback should have been scheduled.
ReactNoop.flushAnimationPri();
expect(ReactNoop.getChildren()).toEqual([span('2')]);
expect(ReactNoop.getChildren()).toEqual([span(2)]);
});
it('flushes all scheduled animation work for many roots', () => {
it('schedules top-level updates in order of priority', () => {
// Initial render.
ReactNoop.render(<span prop={1} />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span(1)]);
ReactNoop.render(<span prop={5} />);
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
ReactNoop.render(<span prop={2} />);
ReactNoop.render(<span prop={3} />);
ReactNoop.render(<span prop={4} />);
});
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
});
expect(ReactNoop.getChildren('a')).toEqual([]);
expect(ReactNoop.getChildren('b')).toEqual([]);
expect(ReactNoop.getChildren('c')).toEqual([]);
ReactNoop.flushAnimationPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
// The low pri update should be flushed last, even though it was scheduled
// before the animation updates.
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span(5)]);
});
it('schedules and flushes deferred work', () => {
ReactNoop.render(<span prop="1" />);
expect(ReactNoop.getChildren()).toEqual([]);
it('schedules top-level updates with same priority in order of insertion', () => {
// Initial render.
ReactNoop.render(<span prop={1} />);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span(1)]);
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren()).toEqual([span('1')]);
});
ReactNoop.render(<span prop={2} />);
ReactNoop.render(<span prop={3} />);
ReactNoop.render(<span prop={4} />);
ReactNoop.render(<span prop={5} />);
it('schedules and flushes deferred work for many roots', () => {
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
expect(ReactNoop.getChildren('a')).toEqual([]);
expect(ReactNoop.getChildren('b')).toEqual([]);
expect(ReactNoop.getChildren('c')).toEqual([]);
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
});
it('flushes scheduled deferred work fitting within deadline', () => {
ReactNoop.render(<span prop="1" />);
ReactNoop.render(<span prop="2" />);
expect(ReactNoop.getChildren()).toEqual([]);
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren()).toEqual([span('2')]);
});
it('flushes scheduled deferred work fitting within deadline for many roots', () => {
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
expect(ReactNoop.getChildren('a')).toEqual([]);
expect(ReactNoop.getChildren('b')).toEqual([]);
expect(ReactNoop.getChildren('c')).toEqual([]);
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
});
it('schedules more deferred work if it runs out of time', () => {
ReactNoop.render(<span prop="1" />);
ReactNoop.render(<span prop="2" />);
expect(ReactNoop.getChildren()).toEqual([]);
ReactNoop.flushDeferredPri(5);
expect(ReactNoop.getChildren()).toEqual([]);
ReactNoop.flushDeferredPri(10);
expect(ReactNoop.getChildren()).toEqual([]);
ReactNoop.flushDeferredPri(10 + 5);
expect(ReactNoop.getChildren()).toEqual([span('2')]);
});
it('schedules more deferred work if it runs out of time with many roots', () => {
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
expect(ReactNoop.getChildren('a')).toEqual([]);
expect(ReactNoop.getChildren('b')).toEqual([]);
expect(ReactNoop.getChildren('c')).toEqual([]);
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 + 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 + 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')]);
});
it('flushes late animation work in a deferred callback if it wins', () => {
// Schedule early deferred
ReactNoop.render(<span prop="1" />);
// Schedule late animation
ReactNoop.performAnimationWork(() => {
ReactNoop.render(<span prop="2" />);
});
expect(ReactNoop.getChildren()).toEqual([]);
// We only scheduled deferred callback so that's what we get.
// It will flush everything.
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren()).toEqual([span('2')]);
});
it('flushes late animation work in a deferred callback if it wins with many roots', () => {
// Schedule early deferred
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
// Schedule late animation
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
});
expect(ReactNoop.getChildren('a')).toEqual([]);
expect(ReactNoop.getChildren('b')).toEqual([]);
expect(ReactNoop.getChildren('c')).toEqual([]);
// We only scheduled deferred callback so that's what we get.
// It will flush everything.
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
});
it('flushes late animation work in an animation callback if it wins', () => {
// Schedule early deferred
ReactNoop.render(<span prop="1" />);
// Schedule late animation
ReactNoop.performAnimationWork(() => {
ReactNoop.render(<span prop="2" />);
});
expect(ReactNoop.getChildren()).toEqual([]);
// Flushing animation should have flushed the animation.
ReactNoop.flushAnimationPri();
expect(ReactNoop.getChildren()).toEqual([span('2')]);
});
it('flushes late animation work in an animation callback if it wins with many roots', () => {
// Schedule early deferred
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
// Schedule late animation
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
});
expect(ReactNoop.getChildren('a')).toEqual([]);
expect(ReactNoop.getChildren('b')).toEqual([]);
expect(ReactNoop.getChildren('c')).toEqual([]);
// Flushing animation should have flushed the animation.
ReactNoop.flushAnimationPri();
expect(ReactNoop.getChildren('a')).toEqual([]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
});
it('flushes all work in a deferred callback if it wins', () => {
// Schedule early animation
ReactNoop.performAnimationWork(() => {
ReactNoop.render(<span prop="1" />);
});
// Schedule late deferred
ReactNoop.render(<span prop="2" />);
expect(ReactNoop.getChildren()).toEqual([]);
// Flushing deferred should have flushed both early animation and late deferred work that invalidated it.
// This is not a common case, as animation should generally be flushed before deferred work.
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren()).toEqual([span('2')]);
});
it('flushes all work in a deferred callback if it wins with many roots', () => {
// Schedule early animation
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
});
// Schedule late deferred
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
expect(ReactNoop.getChildren('a')).toEqual([]);
expect(ReactNoop.getChildren('b')).toEqual([]);
expect(ReactNoop.getChildren('c')).toEqual([]);
// Flushing deferred should have flushed both early animation and late deferred work that invalidated it.
// This is not a common case, as animation should generally be flushed before deferred work.
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
});
it('flushes root with late deferred work in an animation callback if it wins', () => {
// Schedule early animation
ReactNoop.performAnimationWork(() => {
ReactNoop.render(<span prop="1" />);
});
// Schedule late deferred
ReactNoop.render(<span prop="2" />);
expect(ReactNoop.getChildren()).toEqual([]);
// Flushing animation work flushes everything on this root.
ReactNoop.flushAnimationPri();
expect(ReactNoop.getChildren()).toEqual([span('2')]);
});
it('flushes all roots with animation work in an animation callback if it wins', () => {
// Schedule early animation
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
});
// Schedule late deferred
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
expect(ReactNoop.getChildren('a')).toEqual([]);
expect(ReactNoop.getChildren('b')).toEqual([]);
expect(ReactNoop.getChildren('c')).toEqual([]);
// Flushing animation work flushes all roots with animation work.
ReactNoop.flushAnimationPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
expect(ReactNoop.getChildren('c')).toEqual([]);
// Flushing deferred work flushes the root with only deferred work.
ReactNoop.flushDeferredPri();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
});
it('splits deferred work on multiple roots', () => {
// Schedule one root
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
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 + 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 + 5);
expect(ReactNoop.getChildren('a')).toEqual([span('a:2')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
expect(ReactNoop.getChildren('c')).toEqual(null);
// Schedule three roots
ReactNoop.renderToRootWithID(<span prop="a:3" />, 'a');
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 + 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 + 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 + 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')]);
// Schedule one root many times
ReactNoop.renderToRootWithID(<span prop="a:4" />, 'a');
ReactNoop.renderToRootWithID(<span prop="a:5" />, 'a');
ReactNoop.renderToRootWithID(<span prop="a:6" />, 'a');
ReactNoop.flushDeferredPri(15 + 5);
expect(ReactNoop.getChildren('a')).toEqual([span('a:6')]);
ReactNoop.flush();
expect(ReactNoop.getChildren()).toEqual([span(5)]);
});
it('works on deferred roots in the order they were scheduled', () => {
@@ -402,54 +144,6 @@ describe('ReactIncrementalScheduling', () => {
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
});
it('handles interleaved deferred and animation work', () => {
ReactNoop.renderToRootWithID(<span prop="a:1" />, 'a');
ReactNoop.renderToRootWithID(<span prop="b:1" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:1" />, 'c');
ReactNoop.flush();
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:1')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:1')]);
// Schedule both deferred and animation work
ReactNoop.renderToRootWithID(<span prop="a:2" />, 'a');
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="b:2" />, 'b');
ReactNoop.renderToRootWithID(<span prop="c:2" />, 'c');
});
// We're flushing deferred work
// Still, roots with animation work are handled first
ReactNoop.flushDeferredPri(15);
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);
expect(ReactNoop.getChildren('a')).toEqual([span('a:1')]);
expect(ReactNoop.getChildren('b')).toEqual([span('b:2')]);
expect(ReactNoop.getChildren('c')).toEqual([span('c:2')]);
// More deferred and animation work just got scheduled!
ReactNoop.renderToRootWithID(<span prop="c:3" />, 'c');
ReactNoop.performAnimationWork(() => {
ReactNoop.renderToRootWithID(<span prop="b:3" />, 'b');
});
// Animation is still handled first
ReactNoop.flushDeferredPri(15);
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 + 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 + 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')]);
});
it('schedules sync updates when inside componentDidMount/Update', () => {
var instance;
var ops = [];

View File

@@ -169,16 +169,22 @@ describe('ReactCompositeComponent-state', () => {
// componentDidMount() called setState({color:'yellow'}), which is async.
// The update doesn't happen until the next flush.
['componentDidMount-end', 'orange'],
['setState-sunrise', 'orange'],
['setState-orange', 'orange'],
];
// The setState callbacks in componentWillMount, and the initial callback
// passed to ReactDOM.render, should be flushed right after component
// did mount:
expected.push(
['setState-sunrise', 'orange'], // 1
['setState-orange', 'orange'], // 2
['initial-callback', 'orange'], // 3
['shouldComponentUpdate-currentState', 'orange'],
// In Fiber, the initial callback is not enqueued until after any work
// scheduled by lifecycles has flushed (same semantics as a regular setState
// callback outside of a batch). In Stack, the initial render is scheduled
// inside of batchedUpdates, so the callback gets flushed right after
// componentDidMount.
// TODO: We should fix this, in both Stack and Fiber, so that the behavior
// is consistent regardless of whether you're in a batch.
if (!ReactDOMFeatureFlags.useFiber) {
expected.push(['initial-callback', 'orange']);
}
expected.push(['shouldComponentUpdate-currentState', 'orange'],
['shouldComponentUpdate-nextState', 'yellow'],
['componentWillUpdate-currentState', 'orange'],
['componentWillUpdate-nextState', 'yellow'],
@@ -188,6 +194,10 @@ describe('ReactCompositeComponent-state', () => {
['setState-yellow', 'yellow'],
);
if (ReactDOMFeatureFlags.useFiber) {
expected.push(['initial-callback', 'yellow']);
}
expected.push(
['componentWillReceiveProps-start', 'yellow'],
// setState({color:'green'}) only enqueues a pending state.