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 fallthrough
case UpdateState: {
const payload = update.payload;
let partialState;
if (typeof payload === 'function') {
// Updater function
partialState = 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) {
// 只有一个 fiber
queue1 = 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 在什么时候被调用