作者 : 墨成 React 版本 :16.4.1
閱讀本文之前,建議閱讀:
1.基於React 原始碼深入淺出setState:官方文件的啟示錄
2.基於React 原始碼深入淺出setState:setState非同步實現
在上一篇 詳細瞭解了setState的一些機制和原理,同時對 updater 也作了簡單的解釋,這篇文章我們將詳細瞭解這個updater的資料結構和呼叫堆疊.
程式碼如下:
react\packages\react-reconciler\src\ReactFiberClassComponent.js
複製程式碼
const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {
const fiber = ReactInstanceMap.get(inst);
const currentTime = requestCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, fiber);
const update = createUpdate(expirationTime);
update.payload = payload;
if (callback !== undefined && callback !== null) {
if (__DEV__) {
warnOnInvalidCallback(callback, 'setState');
}
update.callback = callback;
}
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
},
enqueueReplaceState(inst, payload, callback) {
const fiber = ReactInstanceMap.get(inst);
const currentTime = requestCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, fiber);
const update = createUpdate(expirationTime);
update.tag = ReplaceState;
update.payload = payload;
if (callback !== undefined && callback !== null) {
if (__DEV__) {
warnOnInvalidCallback(callback, 'replaceState');
}
update.callback = callback;
}
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
},
enqueueForceUpdate(inst, callback) {
const fiber = ReactInstanceMap.get(inst);
const currentTime = requestCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, fiber);
const update = createUpdate(expirationTime);
update.tag = ForceUpdate;
if (callback !== undefined && callback !== null) {
if (__DEV__) {
warnOnInvalidCallback(callback, 'forceUpdate');
}
update.callback = callback;
}
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
},
};複製程式碼
程式碼並不長,但是包含的資訊還是比較多,首先我們知道這個 updater 是個常量物件,而且在整個React它是單例的,即所有元件都是用同一個updater(注意與後面提及的update區別),updater中包含了3個非常重要的方法 enqueueSetState,enqueueForceUpdate,enqueueReplaceState 準確的說是包含了一個重要的方法 enqueueSetState
我們首先看下 在setState中呼叫的 enqueueSetState(inst, payload, callback)
this.updater.enqueueSetState(this, partialState, callback, 'setState'); 複製程式碼
實際的 enqueueSetState(inst, payload, callback)只有三個引數,第四個引數是為預設的ReactNoopUpdateQueue 準備的,可以忽略。
/**
** @param {?react component} 元件事例
* @param {object|function} payload 對應的就是 partialState
* @param {?function} callback 回撥函式
*/
enqueueSetState(inst, payload, callback) {
const fiber = ReactInstanceMap.get(inst);
const currentTime = requestCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, fiber);
const update = createUpdate(expirationTime);
update.payload = payload;
if (callback !== undefined && callback !== null) {
if (__DEV__) {
warnOnInvalidCallback(callback, 'setState');
}
update.callback = callback;
}
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);
}複製程式碼
- 前三個變數 fiber,currentTime,expirationTime
const fiber = ReactInstanceMap.get(inst);
ReactInstanceMap拿到Component的Fiber例項 ,它其實等於
const fiber = inst._reactInternalFiber;
FB的工程師們通過 ReactInstanceMap這樣的實現應該是考慮到後續的擴充和對私有變數取值的封裝,後續 ReactInstanceMap 應該會使一個類似<key,value>的鍵值對資料型別 .
const currentTime = requestCurrentTime();複製程式碼
React通過Performance.now()(如果瀏覽器不支援這個API ,則是用Date.now())獲取時間值(說明:返回當前日期和時間的 Date 物件與'1970/01/01 00:00:00'之間的毫秒值(北京時間的時區為東8區,起點時間實際為:'1970/01/01 08:00:00'),所以這個currentTime是個number型別,即typeof currentTime === 'number' )
const expirationTime = computeExpirationForFiber(currentTime, fiber);複製程式碼
expirationTime根據當前時間和 fiber計算它的 有效時間.expirationTime 涉及的體系比較複雜,後續會結合實際的程式碼講解.
2.Update物件
const update = createUpdate(expirationTime);
update.payload = payload;複製程式碼
建立一個 update 的物件,資料結構在下面會有提及,這裡的payLoad 還是partialState
export function createUpdate(expirationTime: ExpirationTime): Update<*> {
return {
expirationTime: expirationTime,
tag: UpdateState,
payload: null,
callback: null,
next: null,
nextEffect: null,
};
}複製程式碼
3. enqueueUpdate&scheduleWork
故事講到這裡,應該很快進入高潮,兩個比較核心的看點
enqueueUpdate(fiber, update);
scheduleWork(fiber, expirationTime);複製程式碼
在瞭解這兩個函式之前,我們首先看下兩個資料結構
位置:react\packages\react-reconciler\src\ReactUpdateQueue.js
export type Update<State> = {
expirationTime: ExpirationTime,//有效時間
tag: 0 | 1 | 2 | 3,//tag 1 2 3 4 其中之一
payload: any,//update的partialState
callback: (() => mixed) | null,//setState的回撥函式
next: Update<State> | null,//指向下一個 update的指標
nextEffect: Update<State> | null,//指向下一個effect(變化)
};
export type UpdateQueue<State> = {
baseState: State,//就是Component初始化的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,
};複製程式碼
每個update有個指向下一個update的"指標" next, UpdateQueue<State> 是個單向連結串列,firstxxx lastxxx分別指向連結串列頭部和尾部. enqueueUpdate(fiber, update),所以很多人錯誤的認為updateQuenue 是一個(類)陣列, 程式碼如下 :
//以下注釋僅對 ClassComponent有效(即你的元件是繼承React.Component)
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
// Update queues are created lazily. Update queues被延遲建立(即這個元件沒有update 我們沒有必要給它fiber來建立這樣屬性)
const alternate = fiber.alternate;
let queue1;//設計它是為了指向current的updateQueue
let queue2; //設計它是為了指向alternate 的updateQueue
if (alternate === null) {//alternate為null,對於classcomponent第一次呼叫setState時alternate為null
// There's only one fiber.
queue1 = fiber.updateQueue;
queue2 = null;
if (queue1 === null) {//首次呼叫setState建立updateQueue,這時的memoizedState為初始值
queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
}
} else {
// There are two owners.
queue1 = fiber.updateQueue;//current Fiber updateQueue
queue2 = alternate.updateQueue;
if (queue1 === null) {
if (queue2 === null) {
// Neither fiber has an update queue. Create new ones.
//如果都為空,則分別建立,這個case我沒有找到
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.
// 如果有一個有update Queue,則克隆一個
queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);
}
} else {
if (queue2 === null) {
// Only one fiber has an update queue. Clone to create a new one.
queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);
} else {
// Both owners have an update queue.
}
}
}
if (queue2 === null || queue1 === queue2) {
// There's only a single queue.
//一般發生在首次 setState
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.
// 翻譯:如果存在兩個queues,我們需要追加這個 update到這個兩個 queues.
//然而對於這種永續性結構的列表(updateQueue)需要保證一次update不能新增多次
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 = 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;
}
}
}複製程式碼
關於alternate ( 參考React Fiber Architecture)
簡單的概括下:在任何時候,一個元件的例項最多應對兩個fiber:current,alternate(flushed fiber or work-in-progress fiber,不同階段的叫法),alternate是延遲建立並從 cloneFiber這個方法中克隆出來的 ,也就是所謂的persitent structure,他們結構共享(Structural Sharing).
Structural Sharing 和 延遲建立貫穿整個React ,比如這裡
FB 的工程師有段註釋:
// Update queues are created lazily.複製程式碼
update queues是延遲建立,要注意的是,這裡的延遲建立不僅僅是對current,對alternate的queue也是一樣.
scheduleWork 涉及排程器的知識體系,後續學習完成補充著部分的欠缺 。
參考資料 :