Don't drop updates until they are committed

Restructures the update queue to maintain a pointer to the first
pending update, which solves a few problems:

- Updates that occur during the begin phase (e.g. in cWRP of a child)
aren't dropped, like they are currently. This isn't working yet because
the work priority is reset during completion. The following item will
fix it.
- Sets us up to be able to add separate priorities to each update in
the queue. I'll add this in a subsequent commit.
This commit is contained in:
Andrew Clark
2016-12-15 09:12:12 -08:00
parent e36b38c1ca
commit cbba5ef9cc
10 changed files with 363 additions and 195 deletions
+8 -6
View File
@@ -284,17 +284,19 @@ var ReactNoop = {
function logUpdateQueue(updateQueue : UpdateQueue, depth) {
log(
' '.repeat(depth + 1) + 'QUEUED UPDATES',
updateQueue.isReplace ? 'is replace' : '',
updateQueue.isForced ? 'is forced' : ''
' '.repeat(depth + 1) + 'QUEUED UPDATES'
);
const firstPendingUpdate = updateQueue.firstPendingUpdate;
if (!firstPendingUpdate) {
return;
}
log(
' '.repeat(depth + 1) + '~',
updateQueue.partialState,
updateQueue.callback ? 'with callback' : ''
firstPendingUpdate && firstPendingUpdate.partialState,
firstPendingUpdate.callback ? 'with callback' : ''
);
var next;
while (next = updateQueue.next) {
while (next = firstPendingUpdate.next) {
log(
' '.repeat(depth + 1) + '~',
next.partialState,
+4 -7
View File
@@ -100,12 +100,11 @@ export type Fiber = {
pendingProps: any, // This type will be more specific once we overload the tag.
// TODO: I think that there is a way to merge pendingProps and memoizedProps.
memoizedProps: any, // The props used to create the output.
// A queue of local state updates.
updateQueue: ?UpdateQueue,
// The state used to create the output. This is a full state object.
// A queue of state updates and callbacks.
updateQueue: UpdateQueue | null,
// The state used to create the output
memoizedState: any,
// Linked list of callbacks to call after updates are committed.
callbackList: ?UpdateQueue,
// Effect
effectTag: TypeOfSideEffect,
@@ -195,7 +194,6 @@ var createFiber = function(tag : TypeOfWork, key : null | string) : Fiber {
memoizedProps: null,
updateQueue: null,
memoizedState: null,
callbackList: null,
effectTag: NoEffect,
nextEffect: null,
@@ -271,7 +269,6 @@ exports.cloneFiber = function(fiber : Fiber, priorityLevel : PriorityLevel) : Fi
// TODO: Pass in the new pendingProps as an argument maybe?
alt.pendingProps = fiber.pendingProps;
alt.updateQueue = fiber.updateQueue;
alt.callbackList = fiber.callbackList;
alt.pendingWorkPriority = priorityLevel;
alt.memoizedProps = fiber.memoizedProps;
@@ -25,7 +25,10 @@ var {
reconcileChildFibersInPlace,
cloneChildFibers,
} = require('ReactChildFiber');
var {
hasPendingUpdate,
mergeQueue,
} = require('ReactFiberUpdateQueue');
var ReactTypeOfWork = require('ReactTypeOfWork');
var {
getMaskedContext,
@@ -53,6 +56,8 @@ var {
OffscreenPriority,
} = require('ReactPriorityLevel');
var {
Update,
Callback,
Placement,
ContentReset,
Err,
@@ -210,9 +215,29 @@ module.exports = function<T, P, I, TI, C, CX>(
} else {
shouldUpdate = updateClassInstance(current, workInProgress);
}
if (!shouldUpdate) {
// Schedule side-effects
const updateQueue = workInProgress.updateQueue;
if (updateQueue && updateQueue.hasCallback) {
// The update queue has a callback. Schedule a callback effect.
// Callbacks are scheduled regardless of whether we bail out below.
workInProgress.effectTag |= Callback;
}
if (shouldUpdate) {
workInProgress.effectTag |= Update;
} else {
// If an update was already in progress, we should schedule an Update
// effect even though we're bailing out, so that cWU/cDU are called.
if (current) {
const instance = current.stateNode;
if (instance.props !== current.memoizedProps ||
instance.state !== current.memoizedState) {
workInProgress.effectTag |= Update;
}
}
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
// Rerender
const instance = workInProgress.stateNode;
ReactCurrentOwner.current = workInProgress;
@@ -471,13 +496,24 @@ module.exports = function<T, P, I, TI, C, CX>(
workInProgress.child = workInProgress.progressedChild;
}
if ((workInProgress.pendingProps === null || (
workInProgress.memoizedProps !== null &&
workInProgress.pendingProps === workInProgress.memoizedProps
)) &&
workInProgress.updateQueue === null &&
!hasContextChanged()) {
return bailoutOnAlreadyFinishedWork(current, workInProgress);
const pendingProps = workInProgress.pendingProps;
const memoizedProps = workInProgress.memoizedProps;
const updateQueue = workInProgress.updateQueue;
// This is kept as a single expression to take advantage of short-circuiting.
const hasNewProps = (
pendingProps !== null && ( // hasPendingProps && (
memoizedProps === null || // hasNoMemoizedProps ||
pendingProps !== memoizedProps // memoizedPropsDontMatch
) // )
);
if (!hasNewProps) {
const hasUpdate = updateQueue && hasPendingUpdate(updateQueue);
if (!hasUpdate && !hasContextChanged()) {
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
}
switch (workInProgress.tag) {
@@ -497,8 +533,19 @@ module.exports = function<T, P, I, TI, C, CX>(
} else {
pushTopLevelContextObject(root.context, false);
}
if (updateQueue) {
// The last three arguments are unimportant because there should be
// no update functions in a HostRoot's queue.
mergeQueue(updateQueue, null, null, null);
if (updateQueue.hasCallback) {
workInProgress.effectTag |= Callback;
}
}
pushHostContainer(workInProgress.stateNode.containerInfo);
reconcileChildren(current, workInProgress, workInProgress.pendingProps);
reconcileChildren(current, workInProgress, pendingProps);
// A yield component is just a placeholder, we can just run through the
// next one immediately.
return workInProgress.child;
@@ -20,9 +20,11 @@ var {
} = require('ReactFiberContext');
var {
createUpdateQueue,
addToQueue,
addCallbackToQueue,
mergeUpdateQueue,
addUpdate,
addReplaceUpdate,
addForceUpdate,
addCallback,
mergeQueue,
} = require('ReactFiberUpdateQueue');
var { getComponentName, isMounted } = require('ReactFiberTreeReflection');
var ReactInstanceMap = require('ReactInstanceMap');
@@ -49,36 +51,33 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
isMounted,
enqueueSetState(instance, partialState) {
const fiber = ReactInstanceMap.get(instance);
const updateQueue = fiber.updateQueue ?
addToQueue(fiber.updateQueue, partialState) :
createUpdateQueue(partialState);
scheduleUpdateQueue(fiber, updateQueue);
const queue = fiber.updateQueue || createUpdateQueue();
addUpdate(queue, partialState);
scheduleUpdateQueue(fiber, queue);
},
enqueueReplaceState(instance, state) {
const fiber = ReactInstanceMap.get(instance);
const updateQueue = createUpdateQueue(state);
updateQueue.isReplace = true;
scheduleUpdateQueue(fiber, updateQueue);
const queue = fiber.updateQueue || createUpdateQueue();
addReplaceUpdate(queue, state);
scheduleUpdateQueue(fiber, queue);
},
enqueueForceUpdate(instance) {
const fiber = ReactInstanceMap.get(instance);
const updateQueue = fiber.updateQueue || createUpdateQueue(null);
updateQueue.isForced = true;
scheduleUpdateQueue(fiber, updateQueue);
const queue = fiber.updateQueue || createUpdateQueue();
addForceUpdate(queue);
scheduleUpdateQueue(fiber, queue);
},
enqueueCallback(instance, callback) {
const fiber = ReactInstanceMap.get(instance);
let updateQueue = fiber.updateQueue ?
fiber.updateQueue :
createUpdateQueue(null);
addCallbackToQueue(updateQueue, callback);
scheduleUpdateQueue(fiber, updateQueue);
const queue = fiber.updateQueue || createUpdateQueue();
addCallback(queue, callback);
scheduleUpdateQueue(fiber, queue);
},
};
function checkShouldComponentUpdate(workInProgress, oldProps, newProps, newState, newContext) {
const updateQueue = workInProgress.updateQueue;
if (oldProps === null || (updateQueue && updateQueue.isForced)) {
if (oldProps === null || (updateQueue && updateQueue.hasForceUpdate)) {
return true;
}
@@ -245,7 +244,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
// process them now.
const updateQueue = workInProgress.updateQueue;
if (updateQueue) {
instance.state = mergeUpdateQueue(updateQueue, instance, state, props);
instance.state = mergeQueue(updateQueue, instance, state, props);
}
}
}
@@ -294,7 +293,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
// during initial mounting.
const newUpdateQueue = workInProgress.updateQueue;
if (newUpdateQueue) {
newInstance.state = mergeUpdateQueue(newUpdateQueue, newInstance, newState, newProps);
newInstance.state = mergeQueue(newUpdateQueue, newInstance, newState, newProps);
}
return true;
}
@@ -332,11 +331,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
// TODO: Previous state can be null.
let newState;
if (updateQueue) {
if (!updateQueue.hasUpdate) {
newState = oldState;
} else {
newState = mergeUpdateQueue(updateQueue, instance, oldState, newProps);
}
newState = mergeQueue(updateQueue, instance, oldState, newProps);
} else {
newState = oldState;
}
@@ -344,7 +339,7 @@ module.exports = function(scheduleUpdate : (fiber: Fiber) => void) {
if (oldProps === newProps &&
oldState === newState &&
oldContext === newContext &&
updateQueue && !updateQueue.isForced) {
updateQueue && !updateQueue.hasForceUpdate) {
return false;
}
@@ -25,12 +25,11 @@ var {
HostPortal,
CoroutineComponent,
} = ReactTypeOfWork;
var { callCallbacks } = require('ReactFiberUpdateQueue');
var { commitUpdateQueue } = require('ReactFiberUpdateQueue');
var {
Placement,
Update,
Callback,
ContentReset,
} = require('ReactTypeOfSideEffect');
@@ -418,25 +417,16 @@ module.exports = function<T, P, I, TI, C, CX>(
}
attachRef(current, finishedWork, instance);
}
// Clear updates from current fiber.
if (finishedWork.alternate) {
finishedWork.alternate.updateQueue = null;
}
if (finishedWork.effectTag & Callback) {
if (finishedWork.callbackList) {
const callbackList = finishedWork.callbackList;
finishedWork.callbackList = null;
callCallbacks(callbackList, instance);
}
if (finishedWork.updateQueue) {
commitUpdateQueue(finishedWork, finishedWork.updateQueue, instance);
}
return;
}
case HostRoot: {
const rootFiber = finishedWork.stateNode;
if (rootFiber.callbackList) {
const callbackList = rootFiber.callbackList;
rootFiber.callbackList = null;
callCallbacks(callbackList, rootFiber.current.child.stateNode);
const updateQueue = finishedWork.updateQueue;
if (updateQueue) {
const instance = finishedWork.child && finishedWork.child.stateNode;
commitUpdateQueue(finishedWork, updateQueue, instance);
}
return;
}
@@ -41,7 +41,6 @@ var {
} = ReactTypeOfWork;
var {
Update,
Callback,
} = ReactTypeOfSideEffect;
if (__DEV__) {
@@ -73,11 +72,6 @@ module.exports = function<T, P, I, TI, C, CX>(
workInProgress.effectTag |= Update;
}
function markCallback(workInProgress : Fiber) {
// Tag the fiber with a callback effect.
workInProgress.effectTag |= Callback;
}
function appendAllYields(yields : Array<ReifiedYield>, workInProgress : Fiber) {
let node = workInProgress.child;
while (node) {
@@ -187,26 +181,11 @@ module.exports = function<T, P, I, TI, C, CX>(
// Don't use the state queue to compute the memoized state. We already
// merged it and assigned it to the instance. Transfer it from there.
// Also need to transfer the props, because pendingProps will be null
// in the case of an update
// in the case of an update.
const { state, props } = workInProgress.stateNode;
const updateQueue = workInProgress.updateQueue;
workInProgress.memoizedState = state;
workInProgress.memoizedProps = props;
if (current) {
if (current.memoizedProps !== workInProgress.memoizedProps ||
current.memoizedState !== workInProgress.memoizedState ||
updateQueue && updateQueue.isForced) {
markUpdate(workInProgress);
}
} else {
markUpdate(workInProgress);
}
if (updateQueue && updateQueue.hasCallback) {
// Transfer update queue to callbackList field so callbacks can be
// called during commit phase.
workInProgress.callbackList = updateQueue;
markCallback(workInProgress);
}
return null;
case HostRoot: {
workInProgress.memoizedProps = workInProgress.pendingProps;
@@ -215,9 +194,6 @@ module.exports = function<T, P, I, TI, C, CX>(
fiberRoot.context = fiberRoot.pendingContext;
fiberRoot.pendingContext = null;
}
// TODO: Only mark this as an update if we have any pending callbacks
// on it.
markUpdate(workInProgress);
return null;
}
case HostComponent:
@@ -24,7 +24,7 @@ var {
var { createFiberRoot } = require('ReactFiberRoot');
var ReactFiberScheduler = require('ReactFiberScheduler');
var { createUpdateQueue, addCallbackToQueue } = require('ReactFiberUpdateQueue');
var { createUpdateQueue, addCallback } = require('ReactFiberUpdateQueue');
if (__DEV__) {
var ReactFiberInstrumentation = require('ReactFiberInstrumentation');
@@ -109,16 +109,23 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
mountContainer(element : ReactElement<any>, containerInfo : C, parentComponent : ?ReactComponent<any, any, any>, callback: ?Function) : OpaqueNode {
const context = getContextForSubtree(parentComponent);
const root = createFiberRoot(containerInfo, context);
const container = root.current;
if (callback) {
const queue = createUpdateQueue(null);
addCallbackToQueue(queue, callback);
root.callbackList = queue;
}
// TODO: Use pending work/state instead of props.
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.
container.pendingProps = element;
current.pendingProps = element;
if (current.alternate) {
current.alternate.pendingProps = element;
}
if (callback) {
const queue = current.updateQueue || createUpdateQueue();
addCallback(queue, callback);
current.updateQueue = queue;
if (current.alternate) {
current.alternate.updateQueue = queue;
}
}
scheduleWork(root);
@@ -129,24 +136,30 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
// It may seem strange that we don't return the root here, but that will
// allow us to have containers that are in the middle of the tree instead
// of being roots.
return container;
return current;
},
updateContainer(element : ReactElement<any>, 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);
if (callback) {
const queue = root.callbackList ?
root.callbackList :
createUpdateQueue(null);
addCallbackToQueue(queue, callback);
root.callbackList = queue;
}
const current = root.current;
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;
// 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;
}
if (callback) {
const queue = current.updateQueue || createUpdateQueue();
addCallback(queue, callback);
current.updateQueue = queue;
if (current.alternate) {
current.alternate.updateQueue = queue;
}
}
scheduleWork(root);
@@ -13,7 +13,6 @@
'use strict';
import type { Fiber } from 'ReactFiber';
import type { UpdateQueue } from 'ReactFiberUpdateQueue';
const { createHostRootFiber } = require('ReactFiber');
@@ -26,8 +25,6 @@ export type FiberRoot = {
isScheduled: boolean,
// The work schedule is a linked list.
nextScheduledRoot: ?FiberRoot,
// Linked list of callbacks to call after updates are committed.
callbackList: ?UpdateQueue,
// Top context object, used by renderSubtreeIntoContainer
context: Object,
pendingContext: ?Object,
@@ -392,7 +392,6 @@ module.exports = function<T, P, I, TI, C, CX>(config : HostConfig<T, P, I, TI, C
// 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;
@@ -12,100 +12,252 @@
'use strict';
type UpdateQueueNode = {
partialState: any,
callback: ?Function,
import type { Fiber } from 'ReactFiber';
type PartialState<State, Props> =
$Subtype<State> |
(prevState: State, props: Props) => $Subtype<State>;
type Callback = () => void;
type Update = {
partialState: PartialState<any, any>,
callback: Callback | null,
isReplace: boolean,
next: ?UpdateQueueNode,
};
export type UpdateQueue = UpdateQueueNode & {
isForced: boolean,
hasUpdate: boolean,
next: Update | null,
};
export type UpdateQueue = {
// Points to the first (oldest) update.
first: Update | null,
// Points to the first pending update. A pending update is one that is not
// part of the progressed work. This could be null even in a non-empty queue,
// when none of the updates are empty.
firstPendingUpdate: Update | null,
// Points to the last (newest) update.
last: Update | null,
// Used to implement forceUpdate. Only true if there's a merged (non-pending)
// force update; pending force updates do not affect this.
hasForceUpdate: boolean,
// TODO: Remove this by scheduling the side-effect during the begin phase.
hasCallback: boolean,
tail: UpdateQueueNode
};
exports.createUpdateQueue = function(partialState : mixed) : UpdateQueue {
const queue = {
partialState,
callback: null,
isReplace: false,
next: null,
isForced: false,
hasUpdate: partialState != null,
exports.createUpdateQueue = function() : UpdateQueue {
return {
first: null,
firstPendingUpdate: null,
last: null,
hasForceUpdate: false,
hasCallback: false,
tail: (null : any),
};
queue.tail = queue;
return queue;
};
function addToQueue(queue : UpdateQueue, partialState : mixed) : UpdateQueue {
const node = {
partialState,
callback: null,
isReplace: false,
next: null,
};
queue.tail.next = node;
queue.tail = node;
queue.hasUpdate = queue.hasUpdate || (partialState != null);
return queue;
function insertUpdateIntoQueue(queue : UpdateQueue, update : Update) : void {
// Add a pending update to the end of the queue.
// TODO: Once updates have priorities, they should be inserted in the
// correct order. Addtionally, replaceState should remove any pending updates
// that have lower priority from queue.
if (!queue.last) {
// The queue is empty.
queue.first = queue.last = queue.firstPendingUpdate = update;
} else {
// The queue is not empty. Append the update to the end.
queue.last.next = update;
queue.last = update;
if (!queue.firstPendingUpdate) {
// This is the first pending update. Update the pointer.
queue.firstPendingUpdate = update;
}
}
}
exports.addToQueue = addToQueue;
exports.addCallbackToQueue = function(queue : UpdateQueue, callback: Function) : UpdateQueue {
if (queue.tail.callback) {
// If the tail already as a callback, add an empty node to queue
addToQueue(queue, null);
}
queue.tail.callback = callback;
queue.hasCallback = true;
return queue;
exports.addUpdate = function(queue : UpdateQueue, partialState : PartialState<any, any> | null) : void {
const update = {
partialState,
callback: null,
isReplace: false,
isForced: false,
next: null,
};
insertUpdateIntoQueue(queue, update);
};
exports.callCallbacks = function(queue : UpdateQueue, context : any) {
let node : ?UpdateQueueNode = queue;
while (node) {
const callback = node.callback;
if (callback) {
if (typeof context !== 'undefined') {
callback.call(context);
} else {
callback();
exports.addReplaceUpdate = function(queue : UpdateQueue, state : any | null) : void {
const replaceUpdate = {
partialState: state,
callback: null,
isReplace: true,
isForced: false,
next: null,
};
if (!queue.last) {
// The queue is empty.
queue.first = queue.last = queue.firstPendingUpdate = replaceUpdate;
} else {
// The queue is not empty.
// Drop all existing pending updates.
// TODO: Only drop updates with matching priority.
let lastMergedUpdate = null;
if (queue.firstPendingUpdate) {
let node = queue.first;
while (node && node.next !== queue.firstPendingUpdate) {
node = node.next;
}
lastMergedUpdate = node;
} else {
lastMergedUpdate = queue.last;
}
if (lastMergedUpdate) {
// Append the new update to the end of the list.
// $FlowFixMe: Union bug (I think? Getting "object literal - This type incompatible with null")
lastMergedUpdate.next = replaceUpdate;
queue.firstPendingUpdate = replaceUpdate;
queue.last = replaceUpdate;
} else {
// Drop everything
queue.first = queue.firstPendingUpdate = queue.last = replaceUpdate;
}
}
};
exports.addForceUpdate = function(queue : UpdateQueue) : void {
const update = {
partialState: null,
callback: null,
isReplace: false,
isForced: true,
next: null,
};
insertUpdateIntoQueue(queue, update);
};
exports.addCallback = function(queue : UpdateQueue, callback: Callback) : void {
if (queue.firstPendingUpdate && queue.last && !queue.last.callback) {
// If pending updates already exist, and the last pending update does not
// have a callback, we can add the new callback to that update.
// TODO: Add an additional check to ensure the priority matches.
queue.last.callback = callback;
return;
}
const update = {
partialState: null,
callback,
isReplace: false,
isForced: false,
next: null,
};
insertUpdateIntoQueue(queue, update);
};
exports.hasPendingUpdate = function(queue : UpdateQueue) : boolean {
// TODO: Check priority level
return queue.firstPendingUpdate !== null;
};
function getStateFromUpdate(update, instance, prevState, props) {
const partialState = update.partialState;
if (typeof partialState === 'function') {
const updateFn = partialState;
return updateFn.call(instance, prevState, props);
} else {
return partialState;
}
}
// TODO: Move callback effect scheduling here. Rename to beginUpdateQueue or similar.
exports.mergeQueue = function(queue : UpdateQueue, instance : any, prevState : any, props : any) : any {
// This merges the entire update queue into a single object, not just the
// pending updates, because the previous state and props may have changed.
// TODO: Would memoization be worth it?
// Reset these flags. We'll update them while looping through the queue.
queue.hasForceUpdate = false;
queue.hasCallback = false;
let state = prevState;
let dontMutatePrevState = true;
let update : Update | null = queue.first;
let isEmpty = true;
// TODO: Stop merging once we reach an update whose priority doesn't match.
// Should this also apply to updates that were previous merged but bailed out?
while (update) {
let partialState;
if (update.isReplace) {
// A replace should drop all previous updates in the queue, so
// use the original `prevState`, not the accumulated `state`
state = getStateFromUpdate(update, instance, prevState, props);
dontMutatePrevState = true;
isEmpty = false;
} else {
partialState = getStateFromUpdate(update, instance, state, props);
if (partialState) {
if (dontMutatePrevState) {
state = Object.assign({}, state, partialState);
} else {
state = Object.assign(state, partialState);
}
dontMutatePrevState = false;
isEmpty = false;
}
}
node = node.next;
}
};
function getStateFromNode(node, instance, state, props) {
if (typeof node.partialState === 'function') {
const updateFn = node.partialState;
return updateFn.call(instance, state, props);
} else {
return node.partialState;
}
}
exports.mergeUpdateQueue = function(queue : UpdateQueue, instance : any, prevState : any, props : any) : any {
let node : ?UpdateQueueNode = queue;
if (queue.isReplace) {
// replaceState is always first in the queue.
prevState = getStateFromNode(queue, instance, prevState, props);
node = queue.next;
if (!node) {
// If there is no more work, we replace the raw object instead of cloning.
return prevState;
if (update.isForced) {
queue.hasForceUpdate = true;
}
if (update.callback) {
queue.hasCallback = true;
}
update = update.next;
}
let state = Object.assign({}, prevState);
while (node) {
let partialState = getStateFromNode(node, instance, state, props);
Object.assign(state, partialState);
node = node.next;
// The next pending update is the one that we exited on in the loop above.
// Until priorities are implemented, this is always null.
queue.firstPendingUpdate = update;
if (isEmpty) {
// None of the updates contained state. Return the original state object.
return prevState;
}
return state;
};
exports.commitUpdateQueue = function(finishedWork : Fiber, queue : UpdateQueue, context : mixed) {
if (queue.hasCallback) {
// Call the callbacks on all the non-pending updates.
let update = queue.first;
while (update && update !== queue.firstPendingUpdate) {
const callback = update.callback;
if (typeof callback === 'function') {
callback.call(context);
}
update = update.next;
}
}
// Drop all completed updates, leaving only the pending updates.
queue.first = queue.firstPendingUpdate;
if (!queue.first) {
// If the list is now empty, we can remove it from the finished work
finishedWork.updateQueue = null;
if (finishedWork.alternate) {
// Normally we don't mutate the current tree, but we do for updates.
// The queue on the work in progress is always the same as the queue
// on the current.
finishedWork.alternate.updateQueue = null;
}
}
};