mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Merge pull request #8584 from acdlite/fiberhostrootupdatequeue
[Fiber] Queue top-level updates
This commit is contained in:
@@ -90,7 +90,6 @@ src/renderers/shared/shared/__tests__/ReactUpdates-test.js
|
||||
* throws in setState if the update callback is not a function
|
||||
* throws in replaceState if the update callback is not a function
|
||||
* throws in forceUpdate if the update callback is not a function
|
||||
* unmounts and remounts a root in the same batch
|
||||
|
||||
src/renderers/shared/shared/__tests__/refs-test.js
|
||||
* Should increase refs with an increase in divs
|
||||
|
||||
@@ -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
|
||||
@@ -1573,6 +1559,7 @@ src/renderers/shared/shared/__tests__/ReactUpdates-test.js
|
||||
* does not update one component twice in a batch (#2410)
|
||||
* does not update one component twice in a batch (#6371)
|
||||
* unstable_batchedUpdates should return value from a callback
|
||||
* unmounts and remounts a root in the same batch
|
||||
|
||||
src/renderers/shared/shared/__tests__/refs-destruction-test.js
|
||||
* should remove refs when destroying the parent
|
||||
|
||||
@@ -357,7 +357,11 @@ class Surface extends Component {
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
ARTRenderer.unmountContainer(this._mountNode);
|
||||
ARTRenderer.updateContainer(
|
||||
null,
|
||||
this._mountNode,
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
@@ -259,7 +259,7 @@ function warnAboutUnstableUse() {
|
||||
warned = true;
|
||||
}
|
||||
|
||||
function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, element : ReactElement<any>, containerNode : DOMContainerElement | Document, callback: ?Function) {
|
||||
function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any, any>, children : ReactNodeList, containerNode : DOMContainerElement | Document, callback: ?Function) {
|
||||
let container : DOMContainerElement =
|
||||
containerNode.nodeType === DOCUMENT_NODE ? (containerNode : any).documentElement : (containerNode : any);
|
||||
let root;
|
||||
@@ -268,9 +268,9 @@ function renderSubtreeIntoContainer(parentComponent : ?ReactComponent<any, any,
|
||||
while (container.lastChild) {
|
||||
container.removeChild(container.lastChild);
|
||||
}
|
||||
root = container._reactRootContainer = DOMRenderer.mountContainer(element, container, parentComponent, callback);
|
||||
root = container._reactRootContainer = DOMRenderer.mountContainer(children, container, parentComponent, callback);
|
||||
} else {
|
||||
DOMRenderer.updateContainer(element, root = container._reactRootContainer, parentComponent, callback);
|
||||
DOMRenderer.updateContainer(children, root = container._reactRootContainer, parentComponent, callback);
|
||||
}
|
||||
return DOMRenderer.getPublicRootInstance(root);
|
||||
}
|
||||
@@ -292,13 +292,9 @@ var ReactDOM = {
|
||||
|
||||
unmountComponentAtNode(container : DOMContainerElement) {
|
||||
warnAboutUnstableUse();
|
||||
const root = container._reactRootContainer;
|
||||
if (root) {
|
||||
// TODO: Is it safe to reset this now or should I wait since this
|
||||
// unmount could be deferred?
|
||||
return renderSubtreeIntoContainer(null, null, container, () => {
|
||||
container._reactRootContainer = null;
|
||||
DOMRenderer.unmountContainer(root);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
findDOMNode: findDOMNode,
|
||||
|
||||
@@ -329,7 +329,7 @@ const NativeRenderer = ReactFiberReconciler({
|
||||
// But creates an additional child Fiber for raw text children.
|
||||
// No additional native views are created though.
|
||||
// It's not clear to me which is better so I'm deferring for now.
|
||||
// More context @ github.com/facebook/react/pull/8560#discussion_r92111303
|
||||
// More context @ github.com/facebook/react/pull/8560#discussion_r92111303
|
||||
return false;
|
||||
},
|
||||
|
||||
@@ -375,8 +375,9 @@ const ReactNative = {
|
||||
const root = roots.get(containerTag);
|
||||
if (root) {
|
||||
// TODO: Is it safe to reset this now or should I wait since this unmount could be deferred?
|
||||
roots.delete(containerTag);
|
||||
NativeRenderer.unmountContainer(root);
|
||||
NativeRenderer.updateContainer(null, root, null, () => {
|
||||
roots.delete(containerTag);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -198,10 +198,11 @@ var ReactNoop = {
|
||||
|
||||
unmountRootWithID(rootID : string) {
|
||||
const root = roots.get(rootID);
|
||||
roots.delete(rootID);
|
||||
rootContainers.delete(rootID);
|
||||
if (root) {
|
||||
NoopRenderer.unmountContainer(root);
|
||||
NoopRenderer.updateContainer(null, root, null, () => {
|
||||
roots.delete(rootID);
|
||||
rootContainers.delete(rootID);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
@@ -304,14 +305,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 + ']'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
import type { FiberRoot } from 'ReactFiberRoot';
|
||||
import type { PriorityLevel } from 'ReactPriorityLevel';
|
||||
import type { ReactNodeList } from 'ReactTypes';
|
||||
|
||||
var {
|
||||
findCurrentUnmaskedContext,
|
||||
@@ -70,9 +71,8 @@ export type HostConfig<T, P, I, TI, C, CX> = {
|
||||
};
|
||||
|
||||
export type Reconciler<C, I, TI> = {
|
||||
mountContainer(element : ReactElement<any>, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>) : OpaqueNode,
|
||||
updateContainer(element : ReactElement<any>, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>) : void,
|
||||
unmountContainer(container : OpaqueNode) : void,
|
||||
mountContainer(element : ReactNodeList, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>) : OpaqueNode,
|
||||
updateContainer(element : ReactNodeList, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>) : void,
|
||||
performWithPriority(priorityLevel : PriorityLevel, fn : Function) : void,
|
||||
/* eslint-disable no-undef */
|
||||
// FIXME: ESLint complains about type parameter
|
||||
@@ -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,
|
||||
scheduleTopLevelSetState,
|
||||
scheduleUpdateCallback,
|
||||
performWithPriority,
|
||||
batchedUpdates,
|
||||
@@ -108,25 +108,16 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
|
||||
return {
|
||||
|
||||
mountContainer(element : ReactElement<any>, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : OpaqueNode {
|
||||
mountContainer(element : ReactNodeList, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : OpaqueNode {
|
||||
const context = getContextForSubtree(parentComponent);
|
||||
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;
|
||||
}
|
||||
scheduleTopLevelSetState(current, { element });
|
||||
if (callback) {
|
||||
scheduleUpdateCallback(current, callback);
|
||||
}
|
||||
|
||||
scheduleWork(root);
|
||||
|
||||
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
|
||||
ReactFiberInstrumentation.debugTool.onMountContainer(root);
|
||||
}
|
||||
@@ -137,44 +128,27 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
return current;
|
||||
},
|
||||
|
||||
updateContainer(element : ReactElement<any>, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : void {
|
||||
updateContainer(element : ReactNodeList, container : OpaqueNode, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : void {
|
||||
// TODO: If this is a nested container, this won't be the root.
|
||||
const root : FiberRoot = (container.stateNode : any);
|
||||
const current = root.current;
|
||||
|
||||
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;
|
||||
}
|
||||
scheduleTopLevelSetState(current, { element });
|
||||
if (callback) {
|
||||
scheduleUpdateCallback(current, callback);
|
||||
}
|
||||
|
||||
scheduleWork(root);
|
||||
|
||||
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
|
||||
ReactFiberInstrumentation.debugTool.onUpdateContainer(root);
|
||||
}
|
||||
},
|
||||
|
||||
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 = [];
|
||||
}
|
||||
|
||||
scheduleWork(root);
|
||||
|
||||
if (__DEV__ && ReactFiberInstrumentation.debugTool) {
|
||||
ReactFiberInstrumentation.debugTool.onUnmountContainer(root);
|
||||
if (__DEV__) {
|
||||
if (ReactFiberInstrumentation.debugTool) {
|
||||
if (element === null) {
|
||||
ReactFiberInstrumentation.debugTool.onUpdateContainer(root);
|
||||
} else {
|
||||
// This is an unmount
|
||||
ReactFiberInstrumentation.debugTool.onUnmountContainer(root);
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -59,6 +59,7 @@ var {
|
||||
addReplaceUpdate,
|
||||
addForceUpdate,
|
||||
addCallback,
|
||||
addTopLevelUpdate,
|
||||
} = require('ReactFiberUpdateQueue');
|
||||
|
||||
var {
|
||||
@@ -944,28 +945,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 +970,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 +1002,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;
|
||||
@@ -1077,6 +1059,12 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
scheduleUpdateAtPriority(fiber, priorityContext);
|
||||
}
|
||||
|
||||
// TODO: This indirection will be removed as part of #8585
|
||||
function scheduleTopLevelSetState(fiber : Fiber, partialState : any) {
|
||||
addTopLevelUpdate(fiber, partialState, priorityContext);
|
||||
scheduleUpdateAtPriority(fiber, priorityContext);
|
||||
}
|
||||
|
||||
function performWithPriority(priorityLevel : PriorityLevel, fn : Function) {
|
||||
const previousPriorityContext = priorityContext;
|
||||
priorityContext = priorityLevel;
|
||||
@@ -1124,7 +1112,7 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
|
||||
}
|
||||
|
||||
return {
|
||||
scheduleWork: scheduleWork,
|
||||
scheduleTopLevelSetState: scheduleTopLevelSetState,
|
||||
scheduleUpdateCallback: scheduleUpdateCallback,
|
||||
performWithPriority: performWithPriority,
|
||||
batchedUpdates: batchedUpdates,
|
||||
|
||||
@@ -39,6 +39,7 @@ type Update = {
|
||||
callback: Callback | null,
|
||||
isReplace: boolean,
|
||||
isForced: boolean,
|
||||
isTopLevelUnmount: boolean,
|
||||
next: Update | null,
|
||||
};
|
||||
|
||||
@@ -141,6 +142,7 @@ function cloneUpdate(update : Update) : Update {
|
||||
callback: update.callback,
|
||||
isReplace: update.isReplace,
|
||||
isForced: update.isForced,
|
||||
isTopLevelUnmount: update.isTopLevelUnmount,
|
||||
next: null,
|
||||
};
|
||||
}
|
||||
@@ -209,7 +211,9 @@ function findInsertionPosition(queue, update) : Update | null {
|
||||
//
|
||||
// However, if incoming update is inserted into the same position of both lists,
|
||||
// we shouldn't make a copy.
|
||||
function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : void {
|
||||
//
|
||||
// If the update is cloned, it returns the cloned update.
|
||||
function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : Update | null {
|
||||
const queue1 = ensureUpdateQueue(fiber);
|
||||
const queue2 = fiber.alternate ? ensureUpdateQueue(fiber.alternate) : null;
|
||||
|
||||
@@ -244,7 +248,7 @@ function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : vo
|
||||
if (!queue2) {
|
||||
// If there's no alternate queue, there's nothing else to do but insert.
|
||||
insertUpdateIntoQueue(queue1, update, insertAfter1, insertBefore1);
|
||||
return;
|
||||
return null;
|
||||
}
|
||||
|
||||
// If there is an alternate queue, find the insertion position.
|
||||
@@ -260,6 +264,7 @@ function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : vo
|
||||
// insert the clone into the alternate queue.
|
||||
const update2 = cloneUpdate(update);
|
||||
insertUpdateIntoQueue(queue2, update2, insertAfter2, insertBefore2);
|
||||
return update2;
|
||||
} else {
|
||||
// The insertion positions are the same, so when we inserted into the first
|
||||
// queue, it also inserted into the alternate. All we need to do is update
|
||||
@@ -272,6 +277,8 @@ function insertUpdate(fiber : Fiber, update : Update, methodName : ?string) : vo
|
||||
queue2.last = null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function addUpdate(
|
||||
@@ -285,6 +292,7 @@ function addUpdate(
|
||||
callback: null,
|
||||
isReplace: false,
|
||||
isForced: false,
|
||||
isTopLevelUnmount: false,
|
||||
next: null,
|
||||
};
|
||||
if (__DEV__) {
|
||||
@@ -306,6 +314,7 @@ function addReplaceUpdate(
|
||||
callback: null,
|
||||
isReplace: true,
|
||||
isForced: false,
|
||||
isTopLevelUnmount: false,
|
||||
next: null,
|
||||
};
|
||||
|
||||
@@ -324,6 +333,7 @@ function addForceUpdate(fiber : Fiber, priorityLevel : PriorityLevel) : void {
|
||||
callback: null,
|
||||
isReplace: false,
|
||||
isForced: true,
|
||||
isTopLevelUnmount: false,
|
||||
next: null,
|
||||
};
|
||||
if (__DEV__) {
|
||||
@@ -342,6 +352,7 @@ function addCallback(fiber : Fiber, callback: Callback, priorityLevel : Priority
|
||||
callback,
|
||||
isReplace: false,
|
||||
isForced: false,
|
||||
isTopLevelUnmount: false,
|
||||
next: null,
|
||||
};
|
||||
insertUpdate(fiber, update);
|
||||
@@ -353,6 +364,42 @@ function getPendingPriority(queue : UpdateQueue) : PriorityLevel {
|
||||
}
|
||||
exports.getPendingPriority = getPendingPriority;
|
||||
|
||||
function addTopLevelUpdate(
|
||||
fiber : Fiber,
|
||||
partialState : PartialState<any, any>,
|
||||
priorityLevel : PriorityLevel
|
||||
) : void {
|
||||
const isTopLevelUnmount = partialState === null;
|
||||
|
||||
const update = {
|
||||
priorityLevel,
|
||||
partialState,
|
||||
callback: null,
|
||||
isReplace: false,
|
||||
isForced: false,
|
||||
isTopLevelUnmount,
|
||||
next: null,
|
||||
};
|
||||
const update2 = insertUpdate(fiber, update);
|
||||
|
||||
if (isTopLevelUnmount) {
|
||||
// Drop all updates that are lower-priority, so that the tree is not
|
||||
// remounted. We need to do this for both queues.
|
||||
const queue1 = fiber.updateQueue;
|
||||
const queue2 = fiber.alternate && fiber.alternate.updateQueue;
|
||||
|
||||
if (queue1 && update.next) {
|
||||
update.next = null;
|
||||
queue1.last = update;
|
||||
}
|
||||
if (queue2 && update2 && update2.next) {
|
||||
update2.next = null;
|
||||
queue2.last = update;
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.addTopLevelUpdate = addTopLevelUpdate;
|
||||
|
||||
function getStateFromUpdate(update, instance, prevState, props) {
|
||||
const partialState = update.partialState;
|
||||
if (typeof partialState === 'function') {
|
||||
@@ -412,12 +459,14 @@ function beginUpdateQueue(
|
||||
if (update.isForced) {
|
||||
queue.hasForceUpdate = true;
|
||||
}
|
||||
if (update.callback) {
|
||||
// Second condition ignores top-level unmount callbacks if they are not the
|
||||
// last update in the queue, since a subsequent update will cause a remount.
|
||||
if (update.callback && !(update.isTopLevelUnmount && update.next)) {
|
||||
const callbackUpdate = cloneUpdate(update);
|
||||
if (callbackList && callbackList.last) {
|
||||
callbackList.last.next = update;
|
||||
callbackList.last = update;
|
||||
callbackList.last.next = callbackUpdate;
|
||||
callbackList.last = callbackUpdate;
|
||||
} else {
|
||||
const callbackUpdate = cloneUpdate(update);
|
||||
callbackList = {
|
||||
first: callbackUpdate,
|
||||
last: callbackUpdate,
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -654,6 +654,7 @@ describe('ReactIncrementalErrorHandling', () => {
|
||||
ReactNoop.unmountRootWithID('d');
|
||||
ReactNoop.unmountRootWithID('e');
|
||||
ReactNoop.unmountRootWithID('f');
|
||||
ReactNoop.flush();
|
||||
expect(ReactNoop.getChildren('a')).toEqual(null);
|
||||
expect(ReactNoop.getChildren('b')).toEqual(null);
|
||||
expect(ReactNoop.getChildren('c')).toEqual(null);
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user