React更新渲染原理深入分析
當(dāng)我們調(diào)用 setState 之后發(fā)生了什么?react經(jīng)歷了怎樣的過(guò)程將新的 state 渲染到頁(yè)面上?
一次react更新,核心就是對(duì)虛擬dom進(jìn)行diff,找出最少的需要變化的dom節(jié)點(diǎn),然后對(duì)其進(jìn)行相應(yīng)的dom操作,用戶即可在頁(yè)面上看到更新。但 react 作為廣泛使用的框架,需要考慮更多的因素,考慮多個(gè)更新的優(yōu)先級(jí),考慮主線程占用時(shí)長(zhǎng),考慮diff算法復(fù)雜度,考慮性能。。等等,本文就來(lái)探討一下react在其內(nèi)部是如何處理數(shù)據(jù)更新的。
react在內(nèi)部使用fiber這種數(shù)據(jù)結(jié)構(gòu)來(lái)作為虛擬dom【react16+】,它與dom tree一一對(duì)應(yīng),形成fiber tree,一次react更新,本質(zhì)是fiber tree結(jié)構(gòu)的更新變化。而fiber tree結(jié)構(gòu)的更新,用更專(zhuān)業(yè)的術(shù)語(yǔ)來(lái)講,其實(shí)就是fiber tree的協(xié)調(diào)(Reconcile)。Reconcile中文意思是調(diào)和、使一致,協(xié)調(diào)fiber tree,就是調(diào)整fiber tree的結(jié)構(gòu),使其和更新后的jsx模版結(jié)構(gòu)、dom tree保持一致。
react從16起,將更新機(jī)制分為三個(gè)模塊,也可以說(shuō)是三個(gè)步驟,分別是Schedule【調(diào)度】、Reconcile【協(xié)調(diào)】、render【渲染】
Schedule
為什么需要Schedule?
首先我們要知道react在進(jìn)行協(xié)調(diào)時(shí),提供了兩種模式:Legacy mode 同步阻塞模式和 Concurrent mode 并行模式。
不同上下文中的更新會(huì)觸發(fā)不同的模式,如果是在 event、setTimeout、network request 的 callback 中觸發(fā)更新,react 會(huì)采用 Legacy 模式。如果更新與 Suspense、useTransition、OffScreen 相關(guān),那么 react 會(huì)采用 Concurrent 模式。
Legacy mode
Legacy mode在協(xié)調(diào)時(shí)會(huì)啟動(dòng) workLoopSync。workLoopSync 開(kāi)始工作以后,要等到所有 fiber node 都處理完畢以后,才會(huì)結(jié)束工作,也就是 fiber tree 的協(xié)調(diào)過(guò)程不可中斷。
Legacy mode存在的問(wèn)題:如果 fiber tree 的結(jié)構(gòu)很復(fù)雜,那么協(xié)調(diào) fiber tree 可能會(huì)占用大量的時(shí)間,導(dǎo)致主線程會(huì)一直被 js 引擎占用,渲染引擎無(wú)法在規(guī)定時(shí)間(瀏覽器刷新頻率 - 16.7ms)內(nèi)完成工作,使得頁(yè)面出現(xiàn)卡頓(掉幀),影響用戶體驗(yàn)。
Concurrent mode
鑒于Legacy mode存在的問(wèn)題,react團(tuán)隊(duì)在react 16中提出了 Concurrent mode的概念,并在react 18中開(kāi)放使用。react16、17一直為此做準(zhǔn)備。
Concurrent 模式最大的意義在于,使用Concurrent 模式以后的react的應(yīng)用可以做到:
- 協(xié)調(diào)可以中斷、恢復(fù);不會(huì)長(zhǎng)時(shí)間阻塞瀏覽器渲染
- 高優(yōu)先級(jí)更新可以中斷低優(yōu)先級(jí)更新,優(yōu)先渲染
那么,怎么做到這兩點(diǎn)呢?
事實(shí)上,Schedule就是用來(lái)完成這個(gè)任務(wù)的,調(diào)度任務(wù)的優(yōu)先級(jí),使高優(yōu)先級(jí)任務(wù)優(yōu)先進(jìn)入Reconcile,并且提供中斷和恢復(fù)機(jī)制。
時(shí)間切片
react采用時(shí)間切片的方式來(lái)實(shí)現(xiàn)協(xié)調(diào)的中斷和恢復(fù),Concurrent mode在協(xié)調(diào)時(shí)會(huì)啟動(dòng) workLoopConcurrent。 workLoopConcurrent 開(kāi)始工作以后,每次協(xié)調(diào) fiber node 時(shí),都會(huì)判斷當(dāng)前時(shí)間片是否到期。如果時(shí)間片到期,會(huì)停止當(dāng)前 workLoopConcurrent,讓出主線程,然后請(qǐng)求下一個(gè)時(shí)間片繼續(xù)協(xié)調(diào)。
協(xié)調(diào)的中斷及恢復(fù),類(lèi)似于瀏覽器的eventloop,js引擎和渲染引擎互斥,在主線程中交替工作。
我們可以通過(guò)模擬 eventLoop來(lái)實(shí)現(xiàn)時(shí)間分片以及重新請(qǐng)求時(shí)間片。一段 js 程序,如果在規(guī)定時(shí)間內(nèi)沒(méi)有結(jié)束,那我們可以主動(dòng)結(jié)束它,然后請(qǐng)求一個(gè)新的時(shí)間片,在下一個(gè)時(shí)間片內(nèi)繼續(xù)處理上一次沒(méi)有結(jié)束的任務(wù)。
let taskQueue = []; // 任務(wù)列表
let shouldTimeEnd = 5ms; // 一個(gè)時(shí)間片定義為 5ms
let channel = new MessageChannel(); // 創(chuàng)建一個(gè) MessageChannel 實(shí)例
function wookLoop() {
let beginTime = performance.now(); // 記錄開(kāi)始時(shí)間
while(true) { // 循環(huán)處理 taskQueue 中的任務(wù)
let currentTime = performance.now(); // 記錄下一個(gè)任務(wù)開(kāi)始時(shí)的時(shí)間
if (currentTime - beginTime >= shouldTimeEnd) break; // 時(shí)間片已經(jīng)到期,結(jié)束任務(wù)處理
processTask(); // 時(shí)間片沒(méi)有到期,繼續(xù)處理任務(wù)
}
if (taskQueue.length) { // 時(shí)間片到期,通過(guò)調(diào)用 postMessage,請(qǐng)求下一個(gè)時(shí)間片
channel.port2.postMessage(null);
}
}
channel.port1.onmessage = wookLoop; // 在下一個(gè)時(shí)間片內(nèi)繼續(xù)處理任務(wù)
workLoop();
和瀏覽器的消息隊(duì)列 一樣, react 也會(huì)維護(hù)一個(gè)任務(wù)隊(duì)列 taskQueue,然后通過(guò) workLoop 遍歷 taskQueue,依次處理 taskQueue 中的任務(wù)。
taskQueue 中收集任務(wù)是有先后處理順序的,workLoop 每次處理 taskQueue 中的任務(wù)時(shí),都會(huì)挑選優(yōu)先級(jí)最高的任務(wù)進(jìn)行處理。
每觸發(fā)一次 react 更新,意味著一次 fiber tree 的協(xié)調(diào),但協(xié)調(diào)并不會(huì)在更新觸發(fā)時(shí)立刻同步進(jìn)行。相反,react 會(huì)為這一次更新,生成一個(gè) task,并添加到 taskQueue 中,fiber tree 的協(xié)調(diào)方法會(huì)作為新建 task 的 callback。當(dāng) wookLoop 開(kāi)始處理該 task 時(shí),才會(huì)觸發(fā) task 的 callback,開(kāi)始 fiber tree 的協(xié)調(diào)。
任務(wù)的優(yōu)先級(jí)
react在內(nèi)部定義了 5 種類(lèi)型的優(yōu)先級(jí),以及對(duì)應(yīng)的超時(shí)時(shí)間timeout
ImmediatePriority, 直接優(yōu)先級(jí),對(duì)應(yīng)用戶的click、input、focus等操作;timeout為 -1,表示任務(wù)要盡快處理;UserBlockingPriority,用戶阻塞優(yōu)先級(jí),對(duì)應(yīng)用戶的mousemove、scroll等操作;timeout為 250 ms;NormalPriority,普通優(yōu)先級(jí),對(duì)應(yīng)網(wǎng)絡(luò)請(qǐng)求、useTransition等操作;timeout為 5000 ms;LowPriority,低優(yōu)先級(jí)(未找到應(yīng)用場(chǎng)景);timeout為 10000 ms;IdlePriority,空閑優(yōu)先級(jí),如OffScreen;timeout為 1073741823 ms;
5 種優(yōu)先級(jí)的順序?yàn)? ImmediatePriority > UserBlockingPriority > NormalPriority > LowPriority > IdlePriority。
在確定了任務(wù)的優(yōu)先級(jí)以后,react 會(huì)根據(jù)優(yōu)先級(jí)為任務(wù)計(jì)算一個(gè)過(guò)期時(shí)間 expirationTime,即 expirationTime = currentTime + timeout,然后根據(jù) expirationTime 時(shí)間來(lái)決定任務(wù)處理的先后順序。
expirationTime越小的任務(wù)會(huì)被排在task隊(duì)列的越前面,之所以需要timeout,而不是直接對(duì)比優(yōu)先級(jí)等級(jí),是為了避免低優(yōu)先級(jí)任務(wù)長(zhǎng)時(shí)間被 插隊(duì)而導(dǎo)致一直無(wú)響應(yīng);同時(shí),在時(shí)間分片到期時(shí),需要根據(jù)expirationTime判斷下一個(gè)要處理的任務(wù)是否過(guò)期,如果已過(guò)期,就不能讓出主線程,需要立即處理。
??注:react17中用Lanes重構(gòu)了優(yōu)先級(jí)算法,此處不展開(kāi)陳述,有興趣的同學(xué)可查閱相關(guān)文檔。
獲取最先處理的task
react 采用了小頂堆來(lái)存儲(chǔ)task,實(shí)現(xiàn)最小優(yōu)先隊(duì)列,即 taskQueue 是一個(gè)小頂堆,放在堆頂?shù)?code>task是需要最先處理的。
使用最小堆時(shí),有三個(gè)操作:push、pop、peek。
push,入堆操作,即將 task 添加到 taskQueue 中。添加一個(gè)新創(chuàng)建的 task 時(shí),會(huì)將 task 添加到最小堆的堆底,然后對(duì)最小堆做自底向上的調(diào)整。調(diào)整時(shí),會(huì)比較堆節(jié)點(diǎn)(task) 的 expirationTime,將 expirationTime 較小的 task 向上調(diào)整。* peek,獲取堆頂元素,即獲取需要最先處理的 task,執(zhí)行 task 的 callback,開(kāi)始 fiber tree 的協(xié)調(diào)。* pop,堆頂元素出堆,即 task 處理完畢,從 taskQueue 中移除。移除堆頂元素以后,會(huì)對(duì)最小堆做自頂向下的調(diào)整。調(diào)整時(shí),也是比較堆節(jié)點(diǎn)(task) 的 expirationTime,將 expirationTime 較大的 task 向下調(diào)整。### 高優(yōu)先級(jí)的更新中斷低優(yōu)先級(jí)的更新
Concurrent 模式下,如果在低優(yōu)先級(jí)更新的協(xié)調(diào)過(guò)程中,有高優(yōu)先級(jí)更新進(jìn)來(lái),那么高優(yōu)先級(jí)更新會(huì)中斷低優(yōu)先級(jí)更新的協(xié)調(diào)過(guò)程。
每次拿到新的時(shí)間片以后,workLoopConcurrent 都會(huì)判斷本次協(xié)調(diào)對(duì)應(yīng)的優(yōu)先級(jí)和上一次時(shí)間片到期中斷的協(xié)調(diào)的優(yōu)先級(jí)是否一樣。如果一樣,說(shuō)明沒(méi)有更高優(yōu)先級(jí)的更新產(chǎn)生,可以繼續(xù)上次未完成的協(xié)調(diào);如果不一樣,說(shuō)明有更高優(yōu)先級(jí)的更新進(jìn)來(lái),此時(shí)要清空之前已開(kāi)始的協(xié)調(diào)過(guò)程,從根節(jié)點(diǎn)開(kāi)始重新協(xié)調(diào)。等高優(yōu)先級(jí)更新處理完成以后,再次從根節(jié)點(diǎn)開(kāi)始處理低優(yōu)先級(jí)更新。
Reconcile
前面說(shuō)到,reconcile(協(xié)調(diào))就是fiber tree 結(jié)構(gòu)的更新,那么具體是怎樣更新的呢?本小節(jié)就來(lái)解答這個(gè)問(wèn)題。
前置知識(shí)
從jsx到dom

Step1: 從jsx生成react element:
jsx 模板通過(guò) babel 編譯為 createElement 方法;執(zhí)行組件方法,觸發(fā) createElement 的執(zhí)行,返回 react element;
Step2: 從react element生成fiber tree:
fiber tree中存在三種類(lèi)型的指針child、sibling、return。其中,child指向第一個(gè)子節(jié)點(diǎn),sibling指向兄弟節(jié)點(diǎn),return指針指向父節(jié)點(diǎn);*fiber tree采用的深度優(yōu)先遍歷,如果節(jié)點(diǎn)有子節(jié)點(diǎn),先遍歷子節(jié)點(diǎn);子節(jié)點(diǎn)遍歷結(jié)束以后,再遍歷兄弟節(jié)點(diǎn);沒(méi)有子節(jié)點(diǎn)、兄弟節(jié)點(diǎn),就返回父節(jié)點(diǎn),遍歷父節(jié)點(diǎn)的兄弟節(jié)點(diǎn);* 當(dāng)節(jié)點(diǎn)的return指針?lè)祷?null時(shí),fiber tree的遍歷結(jié)束;Step3:fiber tree生成之后,從fiber tree到真實(shí)dom,就是處理fiber tree上對(duì)應(yīng)的副作用,包括:- 所有
dom節(jié)點(diǎn)的新增; componentDidMount、useEffect的callback函數(shù)的觸發(fā);ref引用的初始化;
雙緩存fiber tree
react 做更新處理時(shí),會(huì)同時(shí)存在兩顆 fiber tree。一顆是已經(jīng)存在的 old fiber tree,對(duì)應(yīng)當(dāng)前屏幕顯示的內(nèi)容,稱(chēng)為 current fiber tree;另外一顆是更新過(guò)程中構(gòu)建的 new fiber tree,稱(chēng)為 workInProgress fiber tree。
current fiber tree和workInProgress fiber tree可以通過(guò)alternate指針互相訪問(wèn)
當(dāng)更新完成以后,使用 workInProgress fiber tree 替換掉 current fiber tree,作為下一次更新的 current fiber tree。
協(xié)調(diào)的過(guò)程
協(xié)調(diào)過(guò)程中主要做三件事情:
1.為 workInProgress fiber tree 生成 fiber node;
2.為發(fā)生變化的 fiber node標(biāo)記副作用 effect;
3.收集帶 effect 的 fiber node;
生成workInProgress fiber tree
workInProgress fiber tree 作為一顆新樹(shù),生成 fiber node 的方式有三種:
- 克隆(淺拷貝)
current fiber node,意味著原來(lái)的dom節(jié)點(diǎn)可以復(fù)用,只需要更新dom節(jié)點(diǎn)的屬性,或者移動(dòng)dom節(jié)點(diǎn); - 新建一個(gè)
fiber node,意味著需要新增加一個(gè)dom節(jié)點(diǎn); - 直接復(fù)用
current fiber node,表示對(duì)應(yīng)的dom節(jié)點(diǎn)完全不用做任何處理;
復(fù)用的場(chǎng)景:當(dāng)子組件的渲染方法(類(lèi)組件的 render、函數(shù)組件方法)沒(méi)有觸發(fā),(比如使用了React.memo),沒(méi)有返回新的 react element,子節(jié)點(diǎn)就可以直接復(fù)用 current fiber node。
在日常開(kāi)發(fā)過(guò)程中,我們可以通過(guò)合理使用 ShouldComponentUpdate、React.memo,阻止不必要的組件重新 render,通過(guò)直接復(fù)用 current fiber node,加快 workInProgress fiber tree 的協(xié)調(diào),達(dá)到優(yōu)化的目的。
相反,只要組件的渲染方法被觸發(fā),返回新的 react element,那么就需要根據(jù)新的 react element 為子節(jié)點(diǎn)創(chuàng)建 fiber node(通過(guò)淺拷貝或新建)。
- 如果能在
current fiber tree中找到匹配節(jié)點(diǎn),那么可以通過(guò)克隆(淺拷貝)current fiber node的方式來(lái)創(chuàng)建新的節(jié)點(diǎn); - 相反,如果無(wú)法在
current fiber tree找到匹配節(jié)點(diǎn),那么就需要重新創(chuàng)建一個(gè)新的節(jié)點(diǎn);
我們常說(shuō)的diff算法就是發(fā)生在這一環(huán)節(jié)。
diff算法比較的雙方是 workInProgress fiber tree 中用于構(gòu)建 fiber node 的 react element 和 current fiber tree 中的 fiber node,比較兩者的 key 和 type,根據(jù)比較結(jié)果來(lái)決定如何為 workInProgress fiber tree 創(chuàng)建 fiber node。
【 key 和 type 】:
key就是 jsx 模板中元素上的 key 屬性。如果不寫(xiě)默認(rèn)為undefined。jsx 模板轉(zhuǎn)化為 react element 后,元素的 key 屬性會(huì)作為 react element 的 key 屬性。同樣的,react element 轉(zhuǎn)化為 fiber node 以后,react element 的 key 屬性也會(huì)作為 fiber node 的 key 屬性。
jsx 中不同的元素類(lèi)型,有不同的type:
<Component name="xxxx" /> //type = Component, 是一個(gè)函數(shù) <div></div> // type = "div", 是一個(gè)字符串 <React.Fragment></React.Fragment> // type = React.Fragment, 是一個(gè)數(shù)字(react 內(nèi)部定義的);
jsx 模板轉(zhuǎn)化為 react element 以后,react element 的 type 屬性會(huì)根據(jù) jsx 元素的類(lèi)型賦不同的值,可能是組件函數(shù),也可能是 dom 標(biāo)簽字符串,還可能是數(shù)字。 react element 轉(zhuǎn)化為 fiber node 以后,react element 的 type 屬性也會(huì)作為 fiber node 的 type 屬性。
綜上,判斷拷貝 current fiber node 的邏輯,概括來(lái)就是:
reactElement.key === currentFiberNode.key && reactElement.type === currentFiberNode.type, current fiber node //可以克?。? reactElement.key !== currentFiberNode.key, current fiber node //不可克??; reactElement.key === currentFiberNode.key && reactElement.type !== currentFiberNode.type, current fiber node //不可克??;
diff 算法:
- 已匹配的父節(jié)點(diǎn)的直接子節(jié)點(diǎn)進(jìn)行比較,不跨父節(jié)點(diǎn)比較;
- 通過(guò)比較
key、type來(lái)判斷是否需要克隆current fiber node。只有key和type都相等,才克隆current fiber node作為新的節(jié)點(diǎn),否則就需要新建一個(gè)節(jié)點(diǎn)。key值和節(jié)點(diǎn)類(lèi)型type,key的優(yōu)先級(jí)更高。如果key值不相同,那么節(jié)點(diǎn)不可克隆。 - 當(dāng)比較
single react element和current fiber node list時(shí),只需要遍歷current fiber node list,比較每個(gè)current fiber node和react element的key值和type。只有key和type都相等,react element和current fiber node才能匹配。如果有匹配的,直接克隆current fiber node,作為react element對(duì)應(yīng)的workInProgress fiber node。如果沒(méi)有匹配的current fiber node,就需要為react element重新創(chuàng)建一個(gè)新的fiber node作為workInProgress fiber node。 - 當(dāng)比較
react element list和current fiber node list時(shí),還需要通過(guò)列表下標(biāo)index判斷wokrInProgress fiber node是否相對(duì)于克隆的current fiber node發(fā)生了移動(dòng)。這也是diff中最復(fù)雜的地方。
為發(fā)生變化的fiber node標(biāo)記effect
判斷節(jié)點(diǎn)是否發(fā)生變化
- 節(jié)點(diǎn)只要是重新創(chuàng)建的而不是克隆自
current fiber node,那么節(jié)點(diǎn)就百分之百發(fā)生了變化,需要更新;* 節(jié)點(diǎn)克隆自current fiber node,需要比較props是否發(fā)生了變化,如果props發(fā)生了變化,節(jié)點(diǎn)需要更新;* 節(jié)點(diǎn)克隆自current fiber node,且是組件類(lèi)型,還需要比較state是否發(fā)生了變化,如果state發(fā)生了變化,節(jié)點(diǎn)需要更新;常見(jiàn)的effect類(lèi)型: Placement,放置,只針對(duì)dom類(lèi)型的fiber node,表示節(jié)點(diǎn)需要做移動(dòng)或者添加操作。Update,更新,針對(duì)所有類(lèi)型的fiber node,表示fiber node需要做更新操作。PlacementAndUpdate,放置并更新,只針對(duì)dom類(lèi)型的fiber node,表示節(jié)點(diǎn)發(fā)生了移動(dòng)且props發(fā)生了變化。Ref,表示節(jié)點(diǎn)存在ref,需要初始化 / 更新ref.current。Deletion,刪除,針對(duì)所有類(lèi)型的fiber node,表示fiber node需要移除。Snapshot,快照,主要是針對(duì)類(lèi)組件fiber node。當(dāng)類(lèi)組件fiber node發(fā)生了mount或者update操作,且定義了getSnapshotBeforeUpdate方法,就會(huì)標(biāo)記Snapshot。Passive,主要針對(duì)函數(shù)組件fiber node,表示函數(shù)組件使用了useEffect。當(dāng)函數(shù)組件節(jié)點(diǎn)發(fā)生mount或者update操作,且使用了useEffect hook,就會(huì)給fiber node標(biāo)記Passive。Layout,主要針對(duì)函數(shù)組件fiber node,表示函數(shù)組件使用了useLayoutEffect。當(dāng)函數(shù)組件節(jié)點(diǎn)發(fā)生mount或者update操作,且使用了useLayoutEffect hook,就會(huì)給fiber node標(biāo)記Layout。
react 使用二進(jìn)制數(shù)來(lái)聲明 effect,如 Placement 為 2 (0000 0010),Update 為 4 (0000 0100)。一個(gè) fiber node 可同時(shí)標(biāo)記多個(gè) effect,如函數(shù)組件 props 發(fā)生變化且使用了 useEffect hook,那么就可以使用 Placement | Update = 516(位運(yùn)算符) 來(lái)標(biāo)記。
收集帶effect的fiber node
如果一個(gè) fiber node 被標(biāo)記了 effect,那么 react 就會(huì)在這個(gè) fiber node 完成協(xié)調(diào)以后,將這個(gè) fiber node 收集到effectList中。當(dāng)整顆 fiber tree 完成協(xié)調(diào)以后,所有被標(biāo)記 effect 的 fiber node 都被收集到一起。
收集fiber node的 effectList 采用單鏈表結(jié)構(gòu)存儲(chǔ),firstEffect 指向第一個(gè)標(biāo)記 effect 的 fiber node,lastEffect 標(biāo)記最后一個(gè) fiber node,節(jié)點(diǎn)之間通過(guò) nextEffect 指針連接。
由于 fiber tree 協(xié)調(diào)時(shí)采用的順序是深度優(yōu)先,協(xié)調(diào)完成的順序是子節(jié)點(diǎn)、子節(jié)點(diǎn)兄弟節(jié)點(diǎn)、父節(jié)點(diǎn),所以收集帶 effect 標(biāo)記的 fiber node 時(shí),順序也是子節(jié)點(diǎn)、子節(jié)點(diǎn)兄弟節(jié)點(diǎn)、父節(jié)點(diǎn)。
Render
render也稱(chēng)為commit,是對(duì)協(xié)調(diào)過(guò)程中標(biāo)記的effect 的處理
effect 的處理分為三個(gè)階段,這三個(gè)階段按照從前到后的順序?yàn)椋?/p>
1.before mutation 階段 (dom 操作之前)
2.mutation 階段 (dom 操作)
3.layout 階段 (dom 操作之后)
不同的階段,處理的 effect 種類(lèi)也不相同。在每個(gè)階段,react 都會(huì)從 effectList 鏈表的頭部 - firstEffect 開(kāi)始,按序遍歷 fiber node, 直到 lastEffect。
before mutation階段
before mutation 階段的主要工作是處理帶 Snapshot 標(biāo)記的 fiber node。 從 firstEffect 開(kāi)始遍歷 effect 列表,如果 fiber node 帶 Snapshot 標(biāo)記,觸發(fā) getSnapshotBeforeUpdate 方法。
mutation階段
mutation 階段的主要工作是處理帶 Deletion 、 Placement、PlacementAndUpdate、Update 標(biāo)記的 fiber node。 在這一階段,涉及到 dom 節(jié)點(diǎn)的更新、新增、移動(dòng)、刪除,組件節(jié)點(diǎn)刪除導(dǎo)致的 componentWillUnmount、destory 方法的觸發(fā),以及刪除節(jié)點(diǎn)引發(fā)的 ref 引用的重置。
dom 節(jié)點(diǎn)的更新:
- 通過(guò)原生的 API
setAttribute、removeArrribute修改dom節(jié)點(diǎn)的attr; - 直接修改
dom節(jié)點(diǎn)的style; - 直接修改
dom節(jié)點(diǎn)的innerHtml、textContent;
dom 節(jié)點(diǎn)的新增和移動(dòng):
- 如果新增(移動(dòng))的節(jié)點(diǎn)是父節(jié)點(diǎn)的最后一個(gè)子節(jié)點(diǎn),那么可以直接使用
appendChild方法。 - 如果不是最后一個(gè)節(jié)點(diǎn),需要使用
insertBefore方法。通過(guò)遍歷找到第一個(gè)沒(méi)有帶Placement標(biāo)記的節(jié)點(diǎn)作為insertBefore的定位元素。
dom節(jié)點(diǎn)的刪除:
- 如果節(jié)點(diǎn)是
dom節(jié)點(diǎn),通過(guò)removeChild移除; - 如果節(jié)點(diǎn)是組件節(jié)點(diǎn),觸發(fā)
componentWillUnmount、useEffect的destory方法的執(zhí)行; - 如果標(biāo)記
Deletion的節(jié)點(diǎn)的子節(jié)點(diǎn)中有組件節(jié)點(diǎn),深度優(yōu)先遍歷子節(jié)點(diǎn),依次觸發(fā)子節(jié)點(diǎn)的componentWillUnmount、useEffect的destory方法的執(zhí)行; - 如果標(biāo)記
Deletion的節(jié)點(diǎn)及子節(jié)點(diǎn)關(guān)聯(lián)了ref引用,要將ref引用置空,及ref.current=null(也是深度優(yōu)先遍歷);
layout 階段
layout 階段的主要工作是處理帶 update 標(biāo)記的組件節(jié)點(diǎn)和帶 ref 標(biāo)記的所有節(jié)點(diǎn)。 工作內(nèi)容如下:
- 如果類(lèi)組件節(jié)點(diǎn)是
mount操作,觸發(fā)componentDidMount;如果是update操作,觸發(fā)componentDidUpdate; - 如果函數(shù)組件節(jié)點(diǎn)時(shí)
mount操作,觸發(fā)useLayoutEffect的callback;如果是update操作,先觸發(fā)上一次更新生成的destory,再觸發(fā)這一次的callback; - 異步調(diào)度函數(shù)組件的
useEffect; - 如果組件節(jié)點(diǎn)關(guān)聯(lián)了
ref引用,要初始化ref.current;
到此這篇關(guān)于React更新渲染原理深入分析的文章就介紹到這了,更多相關(guān)React更新渲染內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
react的ui庫(kù)antd中form表單使用SelectTree反顯問(wèn)題及解決
這篇文章主要介紹了react的ui庫(kù)antd中form表單使用SelectTree反顯問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-01-01
React中的useEffect useLayoutEffect到底怎么用
這篇文章主要介紹了React中的useEffect useLayoutEffect具體使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-02-02
React路由規(guī)則定義與聲明式導(dǎo)航及編程式導(dǎo)航分別介紹
這篇文章主要介紹了React路由規(guī)則的定義、聲明式導(dǎo)航、編程式導(dǎo)航,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-09-09
React函數(shù)組件與類(lèi)的區(qū)別有哪些
函數(shù)式組件的基本意義就是,組件實(shí)際上是一個(gè)函數(shù),不是類(lèi),下面這篇文章主要給大家介紹了關(guān)于React中函數(shù)組件與類(lèi)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10
React-RouterV6+AntdV4實(shí)現(xiàn)Menu菜單路由跳轉(zhuǎn)的方法
這篇文章主要介紹了React-RouterV6+AntdV4實(shí)現(xiàn)Menu菜單路由跳轉(zhuǎn),主要有兩種跳轉(zhuǎn)方式一種是編程式跳轉(zhuǎn)另一種是NavLink鏈接式跳轉(zhuǎn),每種方式通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-08-08

