React 深入学习:React 更新队列
生活随笔
收集整理的這篇文章主要介紹了
React 深入学习:React 更新队列
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
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
創建更新
/*** 創建更新* @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
從上面的代碼可以看到,更新 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); } 復制代碼遺留問題
轉載于:https://juejin.im/post/5d04251de51d4556be5b3a49
總結
以上是生活随笔為你收集整理的React 深入学习:React 更新队列的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Android 干货,强烈推荐
- 下一篇: 文本文档TXT每行开头结尾加内容批处理代