NodeNote,持續更新中react相關庫源碼淺析, react ts3 項目react
state初始值爲{a:1}
問題1:最終state會是多少
clickHandler(){
this.setState({
a : 2
})
this.setState({
a : 3
})
this.setState({
a : 4
})
this.setState({
a : 5
})
}
問題2:最終state會是多少
clickHandler(){
this.setState((prevState, nextProps) => {
return {a:prevState.a + 1};
})
this.setState((prevState, nextProps) => {
return {a:prevState.a + 1};
})
this.setState((prevState, nextProps) => {
return {a:prevState.a + 1};
})
this.setState((prevState, nextProps) => {
return {a:prevState.a + 1};
})
}
複製代碼
注意本文將與以前的文章2-6-二、對類組件執行updateClassComponent緊密相關。git
閱讀本文須要特別關注的是:github
緣由:同一事件中屢次同步執行setState的到期時間都是相同的
There's already pending work. We might be in the middle of a browser event. If we were to read the current time, it could cause multiple updates within the same event to receive different expiration times, leading to tearing. Return the last read time. During the next idle callback, the time will be updated 複製代碼
回顧類組件的實例化,給出一些setState相關的準備工做。bash
在執行ReactDOM.render構建fiber樹的時候,遇到類類型的組件,會調用updateClassComponent,該函數會在組件掛載生命週期中調用constructClassInstance實例化類類型組件。app
updateClassComponent所在路徑:react\packages\react-reconciler\src\ReactFiberBeginWork.js函數
constructClassInstance構建類組件的實例而後調用adoptClassInstance(workInProgress, instance)給傳入的instance添加更新器updater屬性值爲classComponentUpdater,這個classComponentUpdater在該文件中是一個包含多個屬性的 對象,主要用於存放更新任務相關邏輯ui
constructClassInstance函數所在路徑:react\packages\react-reconciler\src\ReactFiberClassComponent.js 。this
const classComponentUpdater = {
isMounted,
enqueueSetState(inst, payload, callback) {
...
},
enqueueReplaceState(inst, payload, callback) {
...
},
enqueueForceUpdate(inst, callback) {
...
};
複製代碼
setState是依賴enqueueSetState來執行更新的,enqueueSetState代碼在下面的分析中會給出。spa
setState方法:prototype
Component.prototype.setState = function(partialState, callback) {
...
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
複製代碼
在上述方法中,必需要介紹一下參數表明的意義:
partialState:傳入setState方法的新的state對象
callback:state更新以後的回調函數
複製代碼
在類組價中調用setState會直接調用實例上updater的enqueueSetState方法,其代碼以下:
enqueueSetState(inst, payload, callback) {
//獲取實例對應的fiber
const fiber = ReactInstanceMap.get(inst);
//計算的當前時間:react確保了在同一個時間中全部的更新都是相同的到期時間
const currentTime = requestCurrentTime();
//根據當前時間與fiber計算到期時間
const expirationTime = computeExpirationForFiber(currentTime, fiber);
//傳入一個到期時間,返回一個對象,見packages\react-reconciler\src\ReactUpdateQueue.js
const update = createUpdate(expirationTime);
//利用新的state修改update.payload
update.payload = payload;
//利用callback修改update.callback
if (callback !== undefined && callback !== null) {
if (__DEV__) {
warnOnInvalidCallback(callback, 'setState');
}
update.callback = callback;
}
//???存疑,調用scheduler刪除當前某個任務,這裏先不討論
flushPassiveEffects();
//開始調度setState帶來的更新
enqueueUpdate(fiber, update);
//開始調度新的當前fiber子樹,設置相關的到期時間等等,這裏關鍵是會給當前fiber設置expirationTime爲setState的更新任務相同的值
scheduleWork(fiber, expirationTime);
}
複製代碼
enqueueUpdate爲fiber構建updateQueue隊列,多個setState的效果是多個具有相同到期時間的update都被添加到隊列中
依賴的函數:
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;
}
function appendUpdateToQueue<State>(
queue: UpdateQueue<State>,
update: Update<State>,
) {
// Append the update to the end of the list.
if (queue.lastUpdate === null) {
// Queue is empty
queue.firstUpdate = queue.lastUpdate = update;
} else {
queue.lastUpdate.next = update;
queue.lastUpdate = update;
}
}
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的做用是將傳入的更新任務(包含新state以及到期時間的對象)添加到更新隊列。
export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) {
// Update queues are created lazily.
// 使用alternate屬性雙向鏈接一個當前fiber和其work-in-progress,當前fiber實例的alternate屬性指向其work-in-progress,work-in-progress的alternate屬性指向當前穩定fiber。
const alternate = fiber.alternate;
let queue1;
let queue2;
if (alternate === null) {
// There's only one fiber. queue1 = fiber.updateQueue; queue2 = null; if (queue1 === null) { //若是當前組件沒有等待setState的隊列則建立一個, // 利用fiber當前已經記錄並須要整合的state存儲到queue1與fiber.updateQueue queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState); } } else { // There are two owners. // 若是fiber樹以及workinprogress樹都存在,下面的邏輯則會同步兩個樹的update隊列 queue1 = fiber.updateQueue; queue2 = alternate.updateQueue; // 當兩個樹的隊列至少有一個不存在的時候執行隊列建立或者複製操做 if (queue1 === null) { if (queue2 === null) { // Neither fiber has an update queue. Create new ones. // 兩個隊列都沒有則根據各自的memoizedState建立update隊列 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. 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.
// 若是隻有一個樹,或者兩棵樹隊列是同一個,則將傳入的更新對象添加到第一個隊列中
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. // 有一個隊列不爲空,將update添加到兩個隊列 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; } } } 複製代碼
經過多個同步執行的setState函數的做用,爲fiber生成了對應的updateQueue,而且updateQueue中的每一個update的到期時間是相同的。最終在調度更新樹的時候會調用performWork,其中會調用updateClassInstance,其中會調用processUpdateQueue函數來處理以前構建的updateQueue。只考慮updateQueue中的update,省略CapturedUpdate相關邏輯以後processUpdateQueue部分代碼以下:
export function processUpdateQueue<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
props: any,
instance: any,
renderExpirationTime: ExpirationTime,
): void {
hasForceUpdate = false;
queue = ensureWorkInProgressQueueIsAClone(workInProgress, 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.
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.
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;
}
if (newFirstUpdate === null) {
queue.lastUpdate = null;
}
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;
workInProgress.expirationTime = newExpirationTime;
workInProgress.memoizedState = resultState;
}
複製代碼
在同步屢次執行setState的狀況下,循環中的 if 判斷條件newExpirationTime始終會等於renderExpirationTime,所以會循環遍歷updateQueue隊列中的update,並調用getStateFromUpdate不斷修改合併state,獲得最終的state即resultState。processUpdateQueue的最後會將獲得的新的state存儲到queue.baseState,將queue.firstUpdate置爲null。
接下來經過getStateFromUpdate看如何合併state:
function getStateFromUpdate<State>(
workInProgress: Fiber,
queue: UpdateQueue<State>,
update: Update<State>,
prevState: State,
nextProps: any,
instance: any,
): any {
switch (update.tag) {
...
// Intentional fallthrough
case UpdateState: {
const payload = update.payload;
let partialState;
//兩種不一樣的setState方式:第一個參數是一個對象或者一個函數
if (typeof payload === 'function') {
// Updater function
partialState = payload.call(instance, prevState, nextProps);
} else {
// Partial state object
partialState = payload;
}
if (partialState === null || partialState === undefined) {
// Null and undefined are treated as no-ops.
// 若是合併後的state爲null或者undefined則返回以前的state
return prevState;
}
// Merge the partial state and the previous state.
// 合併state,並返回,Object.assign這裏是第一層深拷貝,若是state比較複雜,就會存在深層屬性淺拷貝的現象
return Object.assign({}, prevState, partialState);
}
...
}
return prevState;
}
複製代碼
因爲這裏只看setState,所以省略了部分代碼。這裏能夠很清晰的看到state的合併機制,setState的第一個參數有兩種方式:
一、對象形式
二、函數形式
(prevState, nextProps) => {
return {...};
}
複製代碼
state初始值爲{a:1}
clickHandler(){
this.setState((prevState, nextProps) => {
return {a:prevState.a + 1};
})
this.setState((prevState, nextProps) => {
return {a:prevState.a + 1};
})
this.setState((prevState, nextProps) => {
return {a:prevState.a + 1};
})
this.setState((prevState, nextProps) => {
return {a:prevState.a + 1};
})
}
複製代碼