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 在什么時候被調用