點擊進入React源碼調試倉庫。
壹旦用戶的交互產生了更新,那麽就會產生壹個update對象去承載新的狀態。多個update會連接成壹個環裝鏈表:updateQueue,掛載fiber上,
然後在該fiber的beginWork階段會循環該updateQueue,依次處理其中的update,這是處理更新的大致過程,也就是計算組件新狀態的本質。在React中,類組件與根組件使用壹類update對象,函數組件則使用另壹類update對象,但是都遵循壹套類似的處理機制。暫且先以類組件的update對象為主進行講解。
更新是如何產生的呢?在類組件中,可以通過調用setState產生壹個更新:
this.setState({val: 6});
而setState實際上會調用enqueueSetState
,生成壹個update對象,並調用enqueueUpdate
將它放入updateQueue。
const classComponentUpdater = {
enqueueSetState(inst, payload, callback) {
...
// 依據事件優先級創建update的優先級
const lane = requestUpdateLane(fiber, suspenseConfig);
const update = createUpdate(eventTime, lane, suspenseConfig);
update.payload = payload;
enqueueUpdate(fiber, update);
// 開始調度
scheduleUpdateOnFiber(fiber, lane, eventTime);
...
},
};
假設B節點產生了更新,那麽B節點的updateQueue最終會是是如下的形態:
A
/
/
B ----- updateQueue.shared.pending = update————
/ ^ |
/ |_______|
C -----> D
updateQueue.shared.pending中存儲著壹個個的update。
下面我們講解以下update和updateQueue的結構。
update對象作為更新的載體,必然要存儲更新的信息
const update: Update<*> = {
eventTime,
lane,
suspenseConfig,
tag: UpdateState,
payload: null,
callback: null,
next: null,
};
在組件上有可能產生多個update,所以對於fiber來說,需要壹個鏈表來存儲這些update,這就是updateQueue,它的結構如下:
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
},
effects: null,
};
我們假設現在產生了壹個更新,那麽以處理這個更新的時刻為基準,來看壹下這些字段的含義:
this.setState({val: 2});
this.setState({val: 6});
產生的updateQueue結構如下:
可以看出它是個單向的環裝鏈表
u1 ---> u2
^ |
|________|
試想壹下,若不使用環狀鏈表,updateQueue指向最後壹個元素,需要遍歷才能獲取鏈表首部。即使將updateQueue指向第壹個元素,那麽新增update時仍然要遍歷到尾部才能將新增的接入鏈表。而環狀鏈表,只需記住尾部,無需遍歷操作就可以找到首部。理解概念是重中之重,下面再來看壹下實現:
function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
const updateQueue = fiber.updateQueue;
if (updateQueue === null) {
return;
}
const sharedQueue: SharedQueue<State> = (updateQueue: any).shared; // ppending是真正的updateQueue,存儲update
const pending = sharedQueue.pending;
if (pending === null) { // 若鏈表中沒有元素,則創建單向環狀鏈表,next指向它自己
update.next = update;
} else {
// 有元素,現有隊列(pending)指向的是鏈表的尾部update,
// pending.next就是頭部update,新update會放到現有隊列的最後
// 並首尾相連
// 將新隊列的尾部(新插入的update)的next指向隊列的首部,實現
// 首位相連
update.next = pending.next; // 現有隊列的最後壹個元素的next指向新來的update,實現把新update
// 接到現有隊列上
pending.next = update;
} // 現有隊列的指針總是指向最後壹個update,可以通過最後壹個尋找出整條鏈表
sharedQueue.pending = update;
}
A1 -> B1 -> C2 -> D1 - E2
字母表示update攜帶的狀態,數字表示update攜帶的優先級。Lanes模型中,可理解為數越小,優先級越高,所以 1 > 2
第壹次以1的渲染優先級處理隊列,遇到C2時,它的優先級不為1,跳過。那麽直到這次處理完updateQueue時,此時的baseUpdate鏈表為
C2 -> D1 - E2
本次更新完成後,firstBaseUpdate 為 C2
,lastBaseUpdate 為 E2
,baseState為ABD
。
用firstBaseUpdate 和 lastBaseUpdate記錄下被跳過的update到最後壹個update的所有update,用baseState記錄下被跳過的update之前那些update所計算出的狀態。這樣做的目的是保證最終updateQueue中所有優先級的update全部處理完時候的結果與預期結果保持壹致。也就是說,盡管A1 -> B1 -> C2 -> D1 - E2
這個鏈表在第壹次以優先級為1去計算的結果為ABD(因為優先級為2的都被跳過了),但最終的結果壹定是ABCDE,因為這是隊列中的所有update對象被全部處理的結果,下邊來詳細剖析updateQueue的處理機制。
處理更新分為三個階段:準備階段、處理階段、完成階段。前兩個階段主要是處理updateQueue,最後壹個階段來將新計算的state賦值到fiber上。
整理updateQueue。由於優先級的原因,會使得低優先級更新被跳過等待下次執行,這個過程中,又有可能產生新的update。所以當處理某次更新的時候,有可能會有兩條update隊列:上次遺留的和本次新增的。上次遺留的就是從firstBaseUpdate 到 lastBaseUpdate 之間的所有update;本次新增的就是新產生的那些的update。
準備階段階段主要是將兩條隊列合並起來,並且合並之後的隊列不再是環狀的,目的方便從頭到尾遍歷處理。另外,由於以上的操作都是處理的workInProgress節點的updateQueue,所以還需要在current節點也操作壹遍,保持同步,目的在渲染被高優先級的任務打斷後,再次以current節點為原型新建workInProgress節點時,不會丟失之前尚未處理的update。
循環處理上壹步整理好的更新隊列。這裏有兩個重點:
優先級不足的update會被跳過,它除了跳過之外,還做了三件事:
第壹次更新的baseState 是空字符串,更新隊列如下,字母表示state,數字表示優先級。優先級是1 > 2的
A1 - B1 - C2 - D1 - E2
第壹次的渲染優先級(renderLanes)為 1,Updates是本次會被處理的隊列:
Base state: ''
Updates: [A1, B1, D1] <- 第壹個被跳過的update為C2,此時的baseUpdate隊列為[C2, D1, E2],
它之前所有被處理的update的結果是AB。此時記錄下baseState = 'AB'
註意!再次跳過低優先級的update(E2)時,則不會記錄baseState
Result state: 'ABD'--------------------------------------------------------------------------------------------------
第二次的渲染優先級(renderLanes)為 2,Updates是本次會被處理的隊列:
Base state: 'AB' <- 再次發起調度時,取出上次更新遺留的baseUpdate隊列,基於baseState
計算結果。
Updates: [C2, D1, E2] Result state: 'ABCDE'
如果某個update優先級足夠,主要是兩件事:
A1 - B2 - C1 - D2
B2被跳過,baseUpdate隊列為
B2 - C1 - D2
這樣做是為了保證最終全部更新完成的結果和用戶行為觸發的那些更新全部完成的預期結果保持壹致。比如,A1和C1雖然在第壹次被優先執行,展現的結果為AC,但這只是為了及時響應用戶交互產生的臨時結果,實際上C1的結果需要依賴B2計算結果,當第二次render時,依據B2的前序update的處理結果(baseState為A)開始處理B2 - C1 - D2隊列,最終的結果是ABCD。在提供的高優先級任務插隊的例子中,可以證明這壹點。
變化過程為 0 -> 2 -> 3,生命周期將state設置為1(任務A2),點擊事件將state + 2(任務A1),正常情況下A2正常調度,但是未render完成,此時A1插隊,更新隊列A2 - A1,為了優先響應高優先級的更新,跳過A2先計算A1,數字由0變為2,baseUpdate為A2 - A1,baseState為0。然後再重做低優先級任務。處理baseUpdate A2 - A1,以baseState(0)為基礎進行計算,最後結果是3。
主要是做壹些賦值和優先級標記的工作。
上面基本把處理更新的所有過程敘述了壹遍,現在讓我們看壹下源碼實現。這部分的代碼在processUpdateQueue
函數中,它裏面涉及到了大量的鏈表操作,代碼比較多,
我們先來看壹下它的結構,我標註出了那三個階段。
function processUpdateQueue<State>(workInProgress: Fiber, props: any, instance: any, renderLanes: Lanes,): void {
// 準備階段
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
let firstBaseUpdate = queue.firstBaseUpdate;
let lastBaseUpdate = queue.lastBaseUpdate;
let pendingQueue = queue.shared.pending;
if (pendingQueue !== null) { /* ... */ }
if (firstBaseUpdate !== null) { // 處理階段
do { ... } while (true);
// 完成階段
if (newLastBaseUpdate === null) {
newBaseState = newState;
}
queue.baseState = ((newBaseState: any): State);
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;
markSkippedUpdateLanes(newLanes);
workInProgress.lanes = newLanes;
workInProgress.memoizedState = newState;
}
}
對於上面的概念與源碼的主體結構了解之後,放出完整代碼,但刪除了無關部分,我添加了註釋,對照著那三個過程來看會更有助於理解,否則單看鏈表操作還是有些復雜。
function processUpdateQueue<State>(
workInProgress: Fiber, props: any, instance: any, renderLanes: Lanes,): void {
// 準備階段----------------------------------------
// 從workInProgress節點上取出updateQueue
// 以下代碼中的queue就是updateQueue
const queue: UpdateQueue<State> = (workInProgress.updateQueue: any);
// 取出queue上的baseUpdate隊列(下面稱遺留的隊列),然後
// 準備接入本次新產生的更新隊列(下面稱新隊列)
let firstBaseUpdate = queue.firstBaseUpdate;
let lastBaseUpdate = queue.lastBaseUpdate;
// 取出新隊列
let pendingQueue = queue.shared.pending;
// 下面的操作,實際上就是將新隊列連接到上次遺留的隊列中。
if (pendingQueue !== null) { queue.shared.pending = null;
// 取到新隊列
const lastPendingUpdate = pendingQueue; const firstPendingUpdate = lastPendingUpdate.next;
// 將遺留的隊列最後壹個元素指向null,實現斷開環狀鏈表
// 然後在尾部接入新隊列
lastPendingUpdate.next = null;
if (lastBaseUpdate === null) {
firstBaseUpdate = firstPendingUpdate;
} else {
// 將遺留的隊列中最後壹個update的next指向新隊列第壹個update
// 完成接入
lastBaseUpdate.next = firstPendingUpdate; } // 修改遺留隊列的尾部為新隊列的尾部
lastBaseUpdate = lastPendingUpdate;
// 用同樣的方式更新current上的firstBaseUpdate 和
// lastBaseUpdate(baseUpdate隊列)。
// 這樣做相當於將本次合並完成的隊列作為baseUpdate隊列備份到current節
// 點上,因為如果本次的渲染被打斷,那麽下次再重新執行任務的時候,workInProgress節點復制
// 自current節點,它上面的baseUpdate隊列會保有這次的update,保證update不丟失。
const current = workInProgress.alternate;
if (current !== null) {
// This is always non-null on a ClassComponent or HostRoot
const currentQueue:UpdateQueue<State> = (current.updateQueue: any);
const currentLastBaseUpdate = currentQueue.lastBaseUpdate;
if (currentLastBaseUpdate !== lastBaseUpdate) {
if (currentLastBaseUpdate === null) {
currentQueue.firstBaseUpdate = firstPendingUpdate;
} else {
currentLastBaseUpdate.next = firstPendingUpdate;
}
currentQueue.lastBaseUpdate = lastPendingUpdate;
}
}
}
// 至此,新隊列已經合並到遺留隊列上,firstBaseUpdate作為
// 這個新合並的隊列,會被循環處理
// 處理階段-------------------------------------
if (firstBaseUpdate !== null) { // 取到baseState
let newState = queue.baseState;
// 聲明newLanes,它會作為本輪更新處理完成的
// 優先級,最終標記到WIP節點上
let newLanes = NoLanes;
// 聲明newBaseState,註意接下來它被賦值的時機,還有前置條件:
// 1. 當有優先級被跳過,newBaseState賦值為newState,
// 也就是queue.baseState
// 2. 當都處理完成後沒有優先級被跳過,newBaseState賦值為
// 本輪新計算的state,最後更新到queue.baseState上
let newBaseState = null;
// 使用newFirstBaseUpdate 和 newLastBaseUpdate // 來表示本次更新產生的的baseUpdate隊列,目的是截取現有隊列中
// 第壹個被跳過的低優先級update到最後的所有update,最後會被更新到
// updateQueue的firstBaseUpdate 和 lastBaseUpdate上
// 作為下次渲染的遺留隊列(baseUpdate)
let newFirstBaseUpdate = null;
let newLastBaseUpdate = null;
// 從頭開始循環
let update = firstBaseUpdate;
do {
const updateLane = update.lane;
const updateEventTime = update.eventTime;
// isSubsetOfLanes函數的意義是,判斷當前更新的優先級(updateLane)
// 是否在渲染優先級(renderLanes)中如果不在,那麽就說明優先級不足
if (!isSubsetOfLanes(renderLanes, updateLane)) {
const clone: Update<State> = {
eventTime: updateEventTime,
lane: updateLane,
suspenseConfig: update.suspenseConfig,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
// 優先級不足,將update添加到本次的baseUpdate隊列中
if (newLastBaseUpdate === null) {
newFirstBaseUpdate = newLastBaseUpdate = clone;
// newBaseState 更新為前壹個 update 任務的結果,下壹輪
// 持有新優先級的渲染過程處理更新隊列時,將會以它為基礎進行計算。
newBaseState = newState;
} else {
// 如果baseUpdate隊列中已經有了update,那麽將當前的update
// 追加到隊列尾部
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
/* *
* newLanes會在最後被賦值到workInProgress.lanes上,而它又最終
* 會被收集到root.pendingLanes。
* 再次更新時會從root上的pendingLanes中找出渲染優先級(renderLanes),
* renderLanes含有本次跳過的優先級,再次進入processUpdateQueue時,
* update的優先級符合要求,被更新掉,低優先級任務因此被重做
* */
newLanes = mergeLanes(newLanes, updateLane);
} else {
if (newLastBaseUpdate !== null) {
// 進到這個判斷說明現在處理的這個update在優先級不足的update之後,
// 原因有二:
// 第壹,優先級足夠;
// 第二,newLastBaseUpdate不為null說明已經有優先級不足的update了
// 然後將這個高優先級放入本次的baseUpdate,實現之前提到的從updateQueue中
// 截取低優先級update到最後壹個update
const clone: Update<State> = {
eventTime: updateEventTime,
lane: NoLane,
suspenseConfig: update.suspenseConfig,
tag: update.tag,
payload: update.payload,
callback: update.callback,
next: null,
};
newLastBaseUpdate = newLastBaseUpdate.next = clone;
}
markRenderEventTimeAndConfig(updateEventTime, update.suspenseConfig);
// 處理更新,計算出新結果
newState = getStateFromUpdate( workInProgress, queue, update, newState, props, instance, );
const callback = update.callback;
// 這裏的callback是setState的第二個參數,屬於副作用,
// 會被放入queue的副作用隊列裏
if (callback !== null) {
workInProgress.effectTag |= Callback;
const effects = queue.effects;
if (effects === null) {
queue.effects = [update];
} else {
effects.push(update);
}
}
} // 移動指針實現遍歷
update = update.next;
if (update === null) {
// 已有的隊列處理完了,檢查壹下有沒有新進來的,有的話
// 接在已有隊列後邊繼續處理
pendingQueue = queue.shared.pending;
if (pendingQueue === null) {
// 如果沒有等待處理的update,那麽跳出循環
break;
} else {
// 如果此時又有了新的update進來,那麽將它接入到之前合並好的隊列中
const lastPendingUpdate = pendingQueue;
const firstPendingUpdate = ((lastPendingUpdate.next: any): Update<State>);
lastPendingUpdate.next = null;
update = firstPendingUpdate;
queue.lastBaseUpdate = lastPendingUpdate;
queue.shared.pending = null;
}
}
} while (true);
// 如果沒有低優先級的更新,那麽新的newBaseState就被賦值為
// 剛剛計算出來的state
if (newLastBaseUpdate === null) {
newBaseState = newState;
}
// 完成階段------------------------------------
queue.baseState = ((newBaseState: any): State);
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate; markSkippedUpdateLanes(newLanes);
workInProgress.lanes = newLanes; workInProgress.memoizedState = newState;
}
}
hooks中useReducer處理更新計算狀態的邏輯與此處基本壹樣。
經過上面的梳理,可以看出來整個對更新的處理都是圍繞優先級。整個processUpdateQueue函數要實現的目的是處理更新,但要保證更新按照優先級被處理的同時,不亂陣腳,這是因為它遵循壹套固定的規則:優先級被跳過後,記住此時的狀態和此優先級之後的更新隊列,並將隊列備份到current節點,這對於update對象按次序、完整地被處理至關重要,也保證了最終呈現的處理結果和用戶的行為觸發的交互的結果保持壹致。