path:packages/react-reconciler/src/ReactUpdateQueue.js
更新
export type Update<State> = {expirationTime: ExpirationTime, // 到期时间tag: 0 | 1 | 2 | 3, // 更新类型payload: any, // 负载callback: (() => mixed) | null, // 回调函数next: Update<State> | null, // 下一个更新nextEffect: Update<State> | null, // 下一个效果
};
复制代码
React 的状态更新分为四种情况,他们分别对应 Update 的 tag 属性的四个值:
- UpdateState
- ReplaceState
- ForceUpdate
- CaptureUpdate
export const UpdateState = 0; // 更新状态
export const ReplaceState = 1; // 替换状态
export const ForceUpdate = 2; // 强制更新
export const CaptureUpdate = 3; // 捕获更新
复制代码
创建更新
/*** 创建更新* @param expirationTime* @returns {{next: null, payload: null, expirationTime: ExpirationTime, callback: null, tag: number, nextEffect: null}}*/
export function createUpdate(expirationTime: ExpirationTime): Update<*> {return {expirationTime: expirationTime,tag: UpdateState,payload: null,callback: null,next: null,nextEffect: null,};
}
复制代码
调用此方法创建的更新默认为是局部更新,需要合并前后状态。
更新队列
export type UpdateQueue<State> = {baseState: State,firstUpdate: Update<State> | null,lastUpdate: Update<State> | null,firstCapturedUpdate: Update<State> | null,lastCapturedUpdate: Update<State> | null,firstEffect: Update<State> | null,lastEffect: Update<State> | null,firstCapturedEffect: Update<State> | null,lastCapturedEffect: Update<State> | null,
};
复制代码
创建更新队列
/*** 创建更新队列* @param baseState* @returns {UpdateQueue<State>}*/
export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {const queue: UpdateQueue<State> = {baseState,firstUpdate: null,lastUpdate: null,firstCapturedUpdate: null,lastCapturedUpdate: null,firstEffect: null,lastEffect: null,firstCapturedEffect: null,lastCapturedEffect: null,};return queue;
}
复制代码
数据结构
从上面的代码中可以看到,更新队列是一个单向链表:
appendUpdateToQueue
追加更新到链表尾部
/*** 添加更新到队列中* @param queue* @param update*/
function appendUpdateToQueue<State>(queue: UpdateQueue<State>,update: Update<State>,
) {// 将更新追加到列表的末尾。if (queue.lastUpdate === null) {// 队列是空的queue.firstUpdate = queue.lastUpdate = update;} else {queue.lastUpdate.next = update;queue.lastUpdate = update;}
}
复制代码
state 更新
每次更新的时候需要根据不同的更新类型来获取下一次的 state:
- UpdateState 需要合并前一次的状态和本次的状态
- ReplaceState 直接使用下一次的状态
- ForceUpdate 使用前一次的状态
- CaptureUpdate
/*** 从跟新获取状态* @param workInProgress* @param queue* @param update* @param prevState* @param nextProps* @param instance* @returns {State|*}*/
function getStateFromUpdate<State>(workInProgress: Fiber,queue: UpdateQueue<State>,update: Update<State>,prevState: State,nextProps: any,instance: any,
): any {switch (update.tag) {case ReplaceState: {const payload = update.payload;if (typeof payload === 'function') {// 更新器函数const nextState = payload.call(instance, prevState, nextProps);return nextState;}// 状态对象return payload;}case CaptureUpdate: {workInProgress.effectTag =(workInProgress.effectTag & ~ShouldCapture) | DidCapture;}// Intentional fallthroughcase UpdateState: {const payload = update.payload;let partialState;if (typeof payload === 'function') {// Updater functionpartialState = payload.call(instance, prevState, nextProps);} else {// 部分状态对象partialState = payload;}if (partialState === null || partialState === undefined) {// Null 和 undefined 被视为 no-ops。return prevState;}// 合并部分状态和前一个状态。return Object.assign({}, prevState, partialState);}case ForceUpdate: {hasForceUpdate = true;return prevState;}}return prevState;
}
复制代码
从上面的代码可以看到,更新 state 时可以接收一个更新器函数,这个更新器函数被绑定到当前的实例上运行,也就是在 React 文档 中写到的,setState
可以接收一个函数作为参数:
setState((prevState, nextProps) => {// do something
})
复制代码
prevState
参数是上一次调用setState
之后的状态,而不是已经更新到 dom 中的状态,因为状态更新是异步的,为了避免不必要的重新渲染来提升性能。nextProps
参数是下一次的 props 对象
处理更新
/*** * @param workInProgress* @param queue* @param props* @param instance* @param renderExpirationTime*/
export function processUpdateQueue<State>(workInProgress: Fiber,queue: UpdateQueue<State>,props: any,instance: any,renderExpirationTime: ExpirationTime,
): void {hasForceUpdate = false;// 确保处理的更新队列的 work 是一个复制品queue = ensureWorkInProgressQueueIsAClone(workInProgress, queue);if (__DEV__) {currentlyProcessingQueue = queue;}// These values may change as we process the queue.// 当我们处理队列时,这些值可能会改变。let newBaseState = queue.baseState;let newFirstUpdate = null;let newExpirationTime = NoWork;// Iterate through the list of updates to compute the result.// 迭代更新列表以计算结果。let update = queue.firstUpdate;let resultState = newBaseState;while (update !== null) {const updateExpirationTime = update.expirationTime;if (updateExpirationTime < renderExpirationTime) {// This update does not have sufficient priority. Skip it.// 此更新没有足够的优先级。跳过它。if (newFirstUpdate === null) {// This is the first skipped update. It will be the first update in// the new list.// 这是第一个跳过的更新。这将是新列表中的第一个更新。newFirstUpdate = update;// Since this is the first update that was skipped, the current result// is the new base state.// 由于这是跳过的第一个更新,所以当前结果是 new base state。newBaseState = resultState;}// Since this update will remain in the list, update the remaining// expiration time.// 由于此更新将保留在列表中,所以更新剩余的过期时间。if (newExpirationTime < updateExpirationTime) {newExpirationTime = updateExpirationTime;}} else {// This update does have sufficient priority. Process it and compute// a new result.// 这次更新确实有足够的优先级。处理它并计算一个新的结果。resultState = getStateFromUpdate(workInProgress,queue,update,resultState,props,instance,);const callback = update.callback;if (callback !== null) {workInProgress.effectTag |= Callback;// Set this to null, in case it was mutated during an aborted render.// 将其设置为null,以防在中止渲染期间发生突变。update.nextEffect = null;if (queue.lastEffect === null) {queue.firstEffect = queue.lastEffect = update;} else {queue.lastEffect.nextEffect = update;queue.lastEffect = update;}}}// Continue to the next update.// 继续下一个更新。update = update.next;}// Separately, iterate though the list of captured updates.// 另外,遍历捕获的更新列表。let newFirstCapturedUpdate = null;update = queue.firstCapturedUpdate;while (update !== null) {const updateExpirationTime = update.expirationTime;if (updateExpirationTime < renderExpirationTime) {// This update does not have sufficient priority. Skip it.// 这个更新没有足够的优先级。跳过它。if (newFirstCapturedUpdate === null) {// This is the first skipped captured update. It will be the first// update in the new list.// 这是第一次跳过捕获的更新。这将是新列表中的第一个更新。newFirstCapturedUpdate = update;// If this is the first update that was skipped, the current result is// the new base state.// 如果这是跳过的第一个更新,则当前结果是新的基本状态。if (newFirstUpdate === null) {newBaseState = resultState;}}// Since this update will remain in the list, update the remaining// expiration time.// 由于此更新将保留在列表中,所以更新剩余的过期时间。if (newExpirationTime < updateExpirationTime) {newExpirationTime = updateExpirationTime;}} else {// This update does have sufficient priority. Process it and compute// a new result.// 这次更新确实有足够的优先级。处理它并计算一个新的结果。resultState = getStateFromUpdate(workInProgress,queue,update,resultState,props,instance,);const callback = update.callback;if (callback !== null) {workInProgress.effectTag |= Callback;// Set this to null, in case it was mutated during an aborted render.// 将其设置为 null,以防在中止 render 期间发生突变。update.nextEffect = null;if (queue.lastCapturedEffect === null) {queue.firstCapturedEffect = queue.lastCapturedEffect = update;} else {queue.lastCapturedEffect.nextEffect = update;queue.lastCapturedEffect = update;}}}update = update.next;}if (newFirstUpdate === null) {queue.lastUpdate = null;}if (newFirstCapturedUpdate === null) {queue.lastCapturedUpdate = null;} else {workInProgress.effectTag |= Callback;}if (newFirstUpdate === null && newFirstCapturedUpdate === null) {// We processed every update, without skipping. That means the new base// state is the same as the result state.// 我们处理了每个更新,没有跳过。这意味着新的基状态与结果状态相同。newBaseState = resultState;}queue.baseState = newBaseState;queue.firstUpdate = newFirstUpdate;queue.firstCapturedUpdate = newFirstCapturedUpdate;// Set the remaining expiration time to be whatever is remaining in the queue.// This should be fine because the only two other things that contribute to// expiration time are props and context. We're already in the middle of the// begin phase by the time we start processing the queue, so we've already// dealt with the props. Context in components that specify// shouldComponentUpdate is tricky; but we'll have to account for// that regardless.// 将剩余的过期时间设置为队列中剩余的时间。// 这应该没问题,因为影响过期时间的另外两个因素是 props 和 context。// 在开始处理队列时,我们已经处于 begin 阶段的中间,// 所以我们已经处理了这些 props。// 指定 shouldComponentUpdate 的组件中的 Context 比较复杂;// 但无论如何我们都要考虑到这一点。workInProgress.expirationTime = newExpirationTime;workInProgress.memoizedState = resultState;if (__DEV__) {currentlyProcessingQueue = null;}
}
复制代码
提交更新
提交更新
/*** 提交更新队列* @param finishedWork* @param finishedQueue* @param instance* @param renderExpirationTime*/
export function commitUpdateQueue<State>(finishedWork: Fiber,finishedQueue: UpdateQueue<State>,instance: any,renderExpirationTime: ExpirationTime,
): void {// 如果已完成的渲染包含捕获的更新,// 并且仍然有较低优先级的更新遗留下来,// 那么我们需要将捕获的更新保存在队列中,// 以便在以较低优先级再次处理队列时重新基于它们,而不是丢弃它们。if (finishedQueue.firstCapturedUpdate !== null) {// 将捕获的更新列表连接到普通列表的末尾。if (finishedQueue.lastUpdate !== null) {finishedQueue.lastUpdate.next = finishedQueue.firstCapturedUpdate;finishedQueue.lastUpdate = finishedQueue.lastCapturedUpdate;}// 清除捕获的更新列表。finishedQueue.firstCapturedUpdate = finishedQueue.lastCapturedUpdate = null;}// 提交效果commitUpdateEffects(finishedQueue.firstEffect, instance);finishedQueue.firstEffect = finishedQueue.lastEffect = null;commitUpdateEffects(finishedQueue.firstCapturedEffect, instance);finishedQueue.firstCapturedEffect = finishedQueue.lastCapturedEffect = null;
}
复制代码
提交更新效果
/*** 提交更新效果* @param effect* @param instance*/
function commitUpdateEffects<State>(effect: Update<State> | null,instance: any,
): void {while (effect !== null) {const callback = effect.callback;if (callback !== null) {effect.callback = null;callCallback(callback, instance);}effect = effect.nextEffect;}
}
复制代码
其他方法
ensureWorkInProgressQueueIsAClone
/*** 确保工作中的处理队列是复制品* 1. 判断当前队列和更新队列是不是相等* 2. 若相等则克隆,若不等则返回当前队列* @param workInProgress* @param queue* @returns {UpdateQueue<State>}*/
function ensureWorkInProgressQueueIsAClone<State>(workInProgress: Fiber,queue: UpdateQueue<State>,
): UpdateQueue<State> {const current = workInProgress.alternate;if (current !== null) {// 如果正在工作的队列等于当前队列,我们需要首先克隆它。if (queue === current.updateQueue) {queue = workInProgress.updateQueue = cloneUpdateQueue(queue);}}return queue;
}
复制代码
cloneUpdateQueue
/*** 克隆更新队列* @param currentQueue* @returns {UpdateQueue<State>}*/
function cloneUpdateQueue<State>(currentQueue: UpdateQueue<State>,
): UpdateQueue<State> {const queue: UpdateQueue<State> = {baseState: currentQueue.baseState,firstUpdate: currentQueue.firstUpdate,lastUpdate: currentQueue.lastUpdate,// TODO: With resuming, if we bail out and resuse the child tree, we should// keep these effects.firstCapturedUpdate: null,lastCapturedUpdate: null,firstEffect: null,lastEffect: null,firstCapturedEffect: null,lastCapturedEffect: null,};return queue;
}
复制代码
enqueueUpdate
/*** 排队更新* @param fiber* @param update*/
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {// 更新队列是惰性创建的。const alternate = fiber.alternate;let queue1;let queue2;if (alternate === null) {// 只有一个 fiberqueue1 = fiber.updateQueue;queue2 = null;if (queue1 === null) {queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);}} else {// 有两个 owner。queue1 = fiber.updateQueue;queue2 = alternate.updateQueue;if (queue1 === null) {if (queue2 === null) {// Neither fiber has an update queue. Create new ones.// 这两种 fiber 都没有更新队列。创造一个新队列。queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);queue2 = alternate.updateQueue = createUpdateQueue(alternate.memoizedState,);} else {// Only one fiber has an update queue. Clone to create a new one.// 只有一个 fiber 有更新队列。克隆以创建一个新的。queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);}} else {if (queue2 === null) {// Only one fiber has an update queue. Clone to create a new one.// 只有一个 fiber 有更新队列。克隆以创建一个新的。queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);} else {// Both owners have an update queue.// 两个所有者都有一个更新队列。}}}if (queue2 === null || queue1 === queue2) {// There's only a single queue.// 只有一个队列。appendUpdateToQueue(queue1, update);} else {// There are two queues. We need to append the update to both queues,// while accounting for the persistent structure of the list — we don't// want the same update to be added multiple times.// 有两个队列。我们需要将更新附加到两个队列,// 同时考虑到列表的持久结构——我们不希望将相同的更新添加多次。if (queue1.lastUpdate === null || queue2.lastUpdate === null) {// One of the queues is not empty. We must add the update to both queues.// 其中一个队列不是空的。我们必须将更新添加到两个队列。appendUpdateToQueue(queue1, update);appendUpdateToQueue(queue2, update);} else {// Both queues are non-empty. The last update is the same in both lists,// because of structural sharing. So, only append to one of the lists.// 两个队列都不是空的。由于结构共享,这两个列表中的最新更新是相同的。// 因此,只向其中一个列表追加。appendUpdateToQueue(queue1, update);// But we still need to update the `lastUpdate` pointer of queue2.// 但是我们仍然需要更新 queue2 的 `lastUpdate` 指针。queue2.lastUpdate = update;}}if (__DEV__) {if (fiber.tag === ClassComponent &&(currentlyProcessingQueue === queue1 ||(queue2 !== null && currentlyProcessingQueue === queue2)) &&!didWarnUpdateInsideUpdate) {warningWithoutStack(false,'An update (setState, replaceState, or forceUpdate) was scheduled ' +'from inside an update function. Update functions should be pure, ' +'with zero side-effects. Consider using componentDidUpdate or a ' +'callback.',);didWarnUpdateInsideUpdate = true;}}
}
复制代码
enqueueCapturedUpdate
/*** 排队捕获的更新* @param workInProgress* @param update*/
export function enqueueCapturedUpdate<State>(workInProgress: Fiber,update: Update<State>,
) {// 捕获的更新进入一个单独的列表,并且只在正在进行的队列中。let workInProgressQueue = workInProgress.updateQueue;if (workInProgressQueue === null) {workInProgressQueue = workInProgress.updateQueue = createUpdateQueue(workInProgress.memoizedState,);} else {// TODO:我把它放在这里,而不是 createWorkInProgress,这样我们就不会不必要地克隆队列。也许有更好的方法来构造它。。workInProgressQueue = ensureWorkInProgressQueueIsAClone(workInProgress,workInProgressQueue,);}// Append the update to the end of the list.// 将更新追加到列表的末尾。if (workInProgressQueue.lastCapturedUpdate === null) {// This is the first render phase update// 这是第一个渲染阶段的更新workInProgressQueue.firstCapturedUpdate = workInProgressQueue.lastCapturedUpdate = update;} else {workInProgressQueue.lastCapturedUpdate.next = update;workInProgressQueue.lastCapturedUpdate = update;}
}
复制代码
callCallback
/*** 调用回调* 1. 回调不存在则抛出错误* 2. 回调存在则使用上下文执行回调** @param callback* @param context*/
function callCallback(callback, context) {invariant(typeof callback === 'function','Invalid argument passed as callback. Expected a function. Instead ' +'received: %s',callback,);callback.call(context);
}
复制代码
遗留问题
- commitUpdateEffects 提交更新效果的时候是根据 Effect 效果的链表进行迭代的?这些 Update 的 nextEffect 是什么时候构成了链表结构?因为我没上面看到的更新队列只是一个 Update 使用 next 组成的一个链表结构
- commitUpdateQueue 提交的时候调用的也是 commitUpdateEffects,传入的 finishedQueue.firstEffect 和 finishedQueue.firstCapturedEffect,createUpdate 是在何处被调用创建了更新的?这些 Effect 又是些什么东西呢?
- 提交更新的时候为什么不是使用 Update.next 而是 Update.nextEffect 呢
- enqueueUpdate、enqueueCapturedUpdate、processUpdateQueue、createUpdate 在什么时候被调用