理解 React 的調(diào)和器 Reconciler
大家好,我是前端西瓜哥。今天來(lái)學(xué)習(xí) React 中的調(diào)和器 Reconciler。
React 的版本為 18.2.0
ReactElement、fiber 和 Dom
ReactElement 就是 React.createElement() 方法的返回結(jié)果,一種 映射真實(shí) DOM 層級(jí)關(guān)系的對(duì)象,但里面可以帶上組件元素。通常我們會(huì)用 JSX 去寫(xiě)。
類組件的 render 方法的返回值和函數(shù)組件的返回值都是 ReactElement。
fiber 是一個(gè)節(jié)點(diǎn),是 React Fiber 時(shí)間分片架構(gòu)中的一個(gè)節(jié)點(diǎn)。fiber 是 ReactElement 和真實(shí) DOM 的橋梁。
fiber 會(huì)根據(jù) ReactElement 構(gòu)建成一棵樹(shù)。當(dāng)更新時(shí),組件會(huì)調(diào)用 render 方法生成新的 ReactElement,然后我們?cè)贅?gòu)建一個(gè)新的 Fiber 樹(shù),和舊樹(shù)對(duì)比。
fiber 樹(shù)下的 fiber 節(jié)點(diǎn)通過(guò)下面三個(gè)字段進(jìn)行關(guān)聯(lián):
- return:父 fiber。
- child:子 fiber 的第一個(gè)。
- sibling :下一個(gè)兄弟節(jié)點(diǎn)。
一個(gè) App 組件的 fiber 樹(shù)結(jié)構(gòu):
App 的 child 是會(huì)指向它創(chuàng)建的 element 對(duì)應(yīng) fiber 的根節(jié)點(diǎn)。
構(gòu)建 Fiber 樹(shù)的過(guò)程
在調(diào)用 createRoot(并發(fā)模式)或者 ReactDOM.render(同步模式)時(shí),會(huì)執(zhí)行 createFiberRoot 方法。
createFiberRoot 創(chuàng)建一個(gè)了 fiberRoot,以及一個(gè) rootFiber。它們的關(guān)系如下:
一個(gè) fiberRoot 是 fiber 樹(shù)的根節(jié)點(diǎn)的維護(hù)者,是 fiberRootNode 實(shí)例,通過(guò) current 確定要使用哪一棵 fiber 樹(shù)。
每調(diào)用 createRoot 都會(huì)構(gòu)建新的 fiber 樹(shù),并生成一個(gè)新的 fiberRoot 去指向它。
rootFiber 是一顆 fiber 樹(shù)的根節(jié)點(diǎn),也屬于是 fiber 節(jié)點(diǎn)。rootFiber 通過(guò)屬性 stateNode 訪問(wèn)到 fiberRoot。注意不是 return,因?yàn)?fiberRoot 的結(jié)構(gòu)完全不一樣,不是 fiber 節(jié)點(diǎn)。
creatRoot 方法返回的是個(gè)對(duì)象,它的 _internalRoot 屬性指向的是這個(gè) fiberRoot。
performUnitOfWork
在 workLoopSync 或 workLoopConcurrent 中,是會(huì)不斷地調(diào)用 performUnitOfWork(workInProgress) 的。
這個(gè)函數(shù)會(huì)不斷地處理以 fiber 為單位的任務(wù)。
workLoopConcurrent 的實(shí)現(xiàn):
看看 performUnitOfWork 的核心實(shí)現(xiàn):
performUnitOfWork 由兩部分組成:
- beginWork:自上而下的調(diào)和,主要在構(gòu)建 fiber。fiber 其實(shí)是對(duì)之前架構(gòu) “遞歸” 的模擬,這個(gè) beginWork 對(duì)應(yīng)著 “遞”。
- completeUnitOfWork:自下而上的合并階段。對(duì)應(yīng)遞歸的 “歸”。
還是這個(gè) fiber 樹(shù)的圖,這里的粉色的 1、2、4 表示的是 beginWork,3、5 則代表 completeUnitOfWork。
beginWork
performUnitOfWork 中,先調(diào)用 beginWork。
beginWork 的作用是,自上而下根據(jù)組件進(jìn)行組件實(shí)例化,根據(jù)新的 element 構(gòu)建 fiber,給舊的 fiber 打上 effectTag。
current 是舊節(jié)點(diǎn),workInProgress 是新節(jié)點(diǎn),屬于半成品,會(huì)在執(zhí)行過(guò)程中一點(diǎn)點(diǎn)填充內(nèi)容。
beginWork 會(huì)進(jìn)行深度優(yōu)先遍歷,生成的新節(jié)點(diǎn) workInProcess 會(huì) tag 屬性進(jìn)入不同的 update 邏輯分支,比如常見(jiàn)的 updateHostRoot、updateClassComponent、updateFunctionComponent 等。
- 會(huì)做組件實(shí)例化,拿到新 ReactElement,然后調(diào)用 reconcileChildFibers 方法進(jìn)行 新舊節(jié)點(diǎn) diff,深度遞歸構(gòu)建子 fiber,形成子 fiber 樹(shù),并把一些數(shù)據(jù)持久化掛載到 fiber 上。
- 在這個(gè)過(guò)程中,會(huì)給 workInProcess 打標(biāo)記。具體是在 fiber.flags 上標(biāo)記 Placement(插入)、Update(更新)等 flags。比如在同層級(jí)進(jìn)行對(duì)比,發(fā)現(xiàn)舊節(jié)點(diǎn)有需要?jiǎng)h除的 fiber,這些 fiber 會(huì)放到新父 fiber 的 deletions 數(shù)組中,并加上 ChildDetetion 標(biāo)簽。
- 這期間穿插調(diào)用了一些生命周期函數(shù)(比如 shouldComponentUpdate),都是是在新舊節(jié)點(diǎn)對(duì)比前,準(zhǔn)確來(lái)說(shuō)是調(diào)用 render 拿到 ReactElement 的時(shí)機(jī)之前。
completeUnitOfWork
performUnitOfWork 中,調(diào)用完 beginWork 后,會(huì)返回一個(gè) next。這個(gè) next 就是下一個(gè)要處理的 fiber,是 unitOfWork 的子 fiber。
這個(gè) next 會(huì)賦值給 workInProgress,然后 workLoopConcurrent 的循環(huán)會(huì) 處理這個(gè)新的 workInProcess。
但當(dāng) next 為 null,就表示找不到下一個(gè) fiber 了,深度的 “遞” 到底了,就要 調(diào)用 completeUnitOfWork,進(jìn)行 “歸” 的收尾工作。
completeUnitOfWork 做的主要工作有:
- 根據(jù) fiber 的類型不同,進(jìn)行不同的處理。對(duì)于原生組件類型(比如 div、span)的掛載階段,會(huì)用 createInstance 創(chuàng)建 一個(gè)真實(shí) DOM,這個(gè) DOM 下還沒(méi)有子節(jié)點(diǎn),然后還會(huì)將其下所有子 fiber 對(duì)應(yīng)的真實(shí) DOM 加到這個(gè) DOM 下,然后賦值給 fiber.stateNode,此時(shí)這個(gè) DOM 元素目前什么屬性都沒(méi)有。
- 根據(jù) props,調(diào)用 setInitialProperties 方法綁定合成事件,以及設(shè)置 DOM 屬性。
React 16 時(shí)會(huì)生成一個(gè) effectList 來(lái)記錄需要更新的節(jié)點(diǎn),防止不必要的遍歷整棵樹(shù)。但 React 17 后被移除掉了,改成從 rootFiber 開(kāi)始從上往下遍歷。
結(jié)尾
調(diào)和階段,主要分為 beginWork 和 completeUnitOfWork 兩部分。
beginWork 自上而下,進(jìn)行新舊節(jié)點(diǎn)對(duì)比,構(gòu)造子 fiber,并打上 flag(插入、更新、刪除),會(huì)執(zhí)行 render(生成新 ReactElement) 之前的生命周期函數(shù)。對(duì)應(yīng)以前 stack reconciler 架構(gòu)中遞歸的 “遞”。
completeUnitOfWork 自下而上,如果是插入,則構(gòu)建真實(shí) DOM 節(jié)點(diǎn)放到 fiber.stateNode 下,接著是處理 props,將屬性添加到 DOM 上。