人人都能讀懂的react源碼解析(大廠高薪必備)
5.state更新流程(setState里到底發(fā)生了什么)
視頻課程&調(diào)試demos
視頻課程的目的是為了快速掌握react源碼運(yùn)行的過(guò)程和react中的scheduler、reconciler、renderer、fiber等,并且詳細(xì)debug源碼和分析,過(guò)程更清晰。
視頻課程&調(diào)試demos
視頻課程的目的是為了快速掌握react源碼運(yùn)行的過(guò)程和react中的scheduler、reconciler、renderer、fiber等,并且詳細(xì)debug源碼和分析,過(guò)程更清晰。
視頻課程:進(jìn)入課程
demos:demo
課程結(jié)構(gòu):
- 開(kāi)篇(聽(tīng)說(shuō)你還在艱難的啃react源碼)
- react心智模型(來(lái)來(lái)來(lái),讓大腦有react思維吧)
- Fiber(我是在內(nèi)存中的dom)
- 從legacy或concurrent開(kāi)始(從入口開(kāi)始,然后讓我們奔向未來(lái))
- state更新流程(setState里到底發(fā)生了什么)
- render階段(厲害了,我有創(chuàng)建Fiber的技能)
- commit階段(聽(tīng)說(shuō)renderer幫我們打好標(biāo)記了,映射真實(shí)節(jié)點(diǎn)吧)
- diff算法(媽媽再也不擔(dān)心我的diff面試了)
- hooks源碼(想知道Function Component是怎樣保存狀態(tài)的嘛)
- scheduler&lane模型(來(lái)看看任務(wù)是暫停、繼續(xù)和插隊(duì)的)
- concurrent mode(并發(fā)模式是什么樣的)
- 手寫迷你react(短小精悍就是我)
上一節(jié)我們介紹了react兩種模式的入口函數(shù)到render階段的調(diào)用過(guò)程,也就是mount首次渲染的流程,這節(jié)我們介紹在更新?tīng)顟B(tài)之后到render階段的流程。
在react中觸發(fā)狀態(tài)更新的幾種方式:
ReactDOM.render
this.setState
this.forceUpdate
useState
-
useReducer
我們重點(diǎn)看下重點(diǎn)看下this.setState和this.forceUpdate,hook在第11章講
1.this.setState內(nèi)調(diào)用this.updater.enqueueSetState
Component.prototype.setState = function (partialState, callback) { if (!(typeof partialState === 'object' || typeof partialState === 'function' || partialState == null)) { { throw Error( "setState(...): takes an object of state variables to update or a function which returns an object of state variables." ); } } this.updater.enqueueSetState(this, partialState, callback, 'setState'); };
2.this.forceUpdate和this.setState一樣,只是會(huì)讓tag賦值ForceUpdate
enqueueForceUpdate(inst, callback) { const fiber = getInstance(inst); const eventTime = requestEventTime(); const suspenseConfig = requestCurrentSuspenseConfig(); const lane = requestUpdateLane(fiber, suspenseConfig); const update = createUpdate(eventTime, lane, suspenseConfig); //tag賦值ForceUpdate update.tag = ForceUpdate; if (callback !== undefined && callback !== null) { update.callback = callback; } enqueueUpdate(fiber, update); scheduleUpdateOnFiber(fiber, lane, eventTime); }, };
如果標(biāo)記ForceUpdate,render階段組件更新會(huì)根據(jù)checkHasForceUpdateAfterProcessing,和checkShouldComponentUpdate來(lái)判斷,如果Update的tag是ForceUpdate,則checkHasForceUpdateAfterProcessing為true,當(dāng)組件是PureComponent時(shí),checkShouldComponentUpdate會(huì)淺比較state和props,所以當(dāng)使用this.forceUpdate一定會(huì)更新
const shouldUpdate =
checkHasForceUpdateAfterProcessing() ||
checkShouldComponentUpdate(
workInProgress,
ctor,
oldProps,
newProps,
oldState,
newState,
nextContext,
);
3.enqueueForceUpdate之后會(huì)經(jīng)歷創(chuàng)建update,調(diào)度update等過(guò)程,接下來(lái)就來(lái)講這些過(guò)程
enqueueSetState(inst, payload, callback) {
const fiber = getInstance(inst);//fiber實(shí)例
const eventTime = requestEventTime();
const suspenseConfig = requestCurrentSuspenseConfig();
const lane = requestUpdateLane(fiber, suspenseConfig);//優(yōu)先級(jí)
const update = createUpdate(eventTime, lane, suspenseConfig);//創(chuàng)建update
update.payload = payload;
if (callback !== undefined && callback !== null) { //賦值回調(diào)
update.callback = callback;
}
enqueueUpdate(fiber, update);//update加入updateQueue
scheduleUpdateOnFiber(fiber, lane, eventTime);//調(diào)度update
}
狀態(tài)更新整體流程
_18
創(chuàng)建Update
HostRoot或者ClassComponent觸發(fā)更新后,會(huì)在函數(shù)createUpdate中創(chuàng)建update,并在后面的render階段的beginWork中計(jì)算Update。FunctionComponent對(duì)應(yīng)的Update在第11章講,它和HostRoot或者ClassComponent的Update結(jié)構(gòu)有些不一樣
export function createUpdate(eventTime: number, lane: Lane): Update<*> {//創(chuàng)建update
const update: Update<*> = {
eventTime,
lane,
tag: UpdateState,
payload: null,
callback: null,
next: null,
};
return update;
}
我們主要關(guān)注這些參數(shù):
lane:優(yōu)先級(jí)(第12章講)
tag:更新的類型,例如UpdateState、ReplaceState
payload:ClassComponent的payload是setState第一個(gè)參數(shù),HostRoot的payload是ReactDOM.render的第一個(gè)參數(shù)
callback:setState的第二個(gè)參數(shù)
next:連接下一個(gè)Update形成一個(gè)鏈表,例如同時(shí)觸發(fā)多個(gè)setState時(shí)會(huì)形成多個(gè)Update,然后用next 連接
updateQueue:
對(duì)于HostRoot或者ClassComponent會(huì)在mount的時(shí)候使用initializeUpdateQueue創(chuàng)建updateQueue,然后將updateQueue掛載到fiber節(jié)點(diǎn)上
export function initializeUpdateQueue<State>(fiber: Fiber): void {
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState,
firstBaseUpdate: null,
lastBaseUpdate: null,
shared: {
pending: null,
},
effects: null,
};
fiber.updateQueue = queue;
}
-
baseState:初始state,后面會(huì)基于這個(gè)state,根據(jù)Update計(jì)算新的state
- firstBaseUpdate、lastBaseUpdate:Update形成的鏈表的頭和尾
- shared.pending:新產(chǎn)生的update會(huì)以單向環(huán)狀鏈表保存在shared.pending上,計(jì)算state的時(shí)候會(huì)剪開(kāi)這個(gè)環(huán)狀鏈表,并且鏈接在lastBaseUpdate后
- effects:calback不為null的update
從fiber節(jié)點(diǎn)向上遍歷到rootFiber
在markUpdateLaneFromFiberToRoot函數(shù)中會(huì)從觸發(fā)更新的節(jié)點(diǎn)開(kāi)始向上遍歷到rootFiber,遍歷的過(guò)程會(huì)處理節(jié)點(diǎn)的優(yōu)先級(jí)(第12章講)
function markUpdateLaneFromFiberToRoot( sourceFiber: Fiber, lane: Lane, ): FiberRoot | null { sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane); let alternate = sourceFiber.alternate; if (alternate !== null) { alternate.lanes = mergeLanes(alternate.lanes, lane); } let node = sourceFiber; let parent = sourceFiber.return; while (parent !== null) {//從觸發(fā)更新的節(jié)點(diǎn)開(kāi)始向上遍歷到rootFiber parent.childLanes = mergeLanes(parent.childLanes, lane);//合并childLanes優(yōu)先級(jí) alternate = parent.alternate; if (alternate !== null) { alternate.childLanes = mergeLanes(alternate.childLanes, lane); } else { } node = parent; parent = parent.return; } if (node.tag === HostRoot) { const root: FiberRoot = node.stateNode; return root; } else { return null; } }
調(diào)度
在ensureRootIsScheduled中,scheduleCallback會(huì)以一個(gè)優(yōu)先級(jí)調(diào)度render階段的開(kāi)始函數(shù)performSyncWorkOnRoot或者performConcurrentWorkOnRoot
if (newCallbackPriority === SyncLanePriority) {
// 任務(wù)已經(jīng)過(guò)期,需要同步執(zhí)行render階段
newCallbackNode = scheduleSyncCallback(
performSyncWorkOnRoot.bind(null, root)
);
} else {
// 根據(jù)任務(wù)優(yōu)先級(jí)異步執(zhí)行render階段
var schedulerPriorityLevel = lanePriorityToSchedulerPriority(
newCallbackPriority
);
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root)
);
}
狀態(tài)更新
classComponent狀態(tài)計(jì)算發(fā)生在processUpdateQueue函數(shù)中,涉及很多鏈表操作,看圖更加直白
初始時(shí)fiber.updateQueue單鏈表上有firstBaseUpdate(update1)和lastBaseUpdate(update2),以next連接
fiber.updateQueue.shared環(huán)狀鏈表上有update3和update4,以next連接互相連接
計(jì)算state時(shí),先將fiber.updateQueue.shared環(huán)狀鏈表‘剪開(kāi)’,形成單鏈表,連接在fiber.updateQueue后面形成baseUpdate
然后遍歷按這條鏈表,根據(jù)baseState計(jì)算出memoizedState
帶優(yōu)先級(jí)的狀態(tài)更新
類似git提交,這里的c3意味著高優(yōu)先級(jí)的任務(wù),比如用戶出發(fā)的事件,數(shù)據(jù)請(qǐng)求,同步執(zhí)行的代碼等。
-
通過(guò)ReactDOM.render創(chuàng)建的應(yīng)用沒(méi)有優(yōu)先級(jí)的概念,類比git提交,相當(dāng)于先commit,然后提交c3
_20 -
在concurrent模式下,類似git rebase,先暫存之前的代碼,在master上開(kāi)發(fā),然后rebase到之前的分支上
優(yōu)先級(jí)是由Scheduler來(lái)調(diào)度的,這里我們只關(guān)心狀態(tài)計(jì)算時(shí)的優(yōu)先級(jí)排序,也就是在函數(shù)processUpdateQueue中發(fā)生的計(jì)算,例如初始時(shí)有c1-c4四個(gè)update,其中c1和c3為高優(yōu)先級(jí)
在第一次render的時(shí)候,低優(yōu)先級(jí)的update會(huì)跳過(guò),所以只有c1和c3加入狀態(tài)的計(jì)算
-
在第二次render的時(shí)候,會(huì)以第一次中跳過(guò)的update(c2)之前的update(c1)作為baseState,跳過(guò)的update和之后的update(c2,c3,c4)作為baseUpdate重新計(jì)算
在在concurrent模式下,componentWillMount可能會(huì)執(zhí)行多次,變現(xiàn)和之前的版本不一致
注意,fiber.updateQueue.shared會(huì)同時(shí)存在于workInprogress Fiber和current Fiber,目的是為了防止高優(yōu)先級(jí)打斷正在進(jìn)行的計(jì)算而導(dǎo)致?tīng)顟B(tài)丟失,這段代碼也是發(fā)生在processUpdateQueue中