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-01React中的useEffect useLayoutEffect到底怎么用
這篇文章主要介紹了React中的useEffect useLayoutEffect具體使用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-02-02React路由規(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-09React函數(shù)組件與類(lèi)的區(qū)別有哪些
函數(shù)式組件的基本意義就是,組件實(shí)際上是一個(gè)函數(shù),不是類(lèi),下面這篇文章主要給大家介紹了關(guān)于React中函數(shù)組件與類(lèi)的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-10-10React-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