簡(jiǎn)單分析React中的EffectList
React中,會(huì)遍歷EffectList來(lái)執(zhí)行節(jié)點(diǎn)操作、生命周期方法、Effect方法,可以把EffectList比作圣誕樹上掛的彩燈,而這顆圣誕樹就是Fiber樹。
為什么會(huì)存在EffectList呢?打個(gè)比方來(lái)說(shuō),一顆Fiber樹中有一些Fiber節(jié)點(diǎn)需要執(zhí)行componentDidMount
方法,如果在Fiber樹構(gòu)建完成后,再遍歷一次Fiber樹,找到需要執(zhí)行componentDidMount
方法的Fiber節(jié)點(diǎn),這是非常低效的。
而EffectList就解決了這個(gè)問(wèn)題,在Fiber樹構(gòu)建過(guò)程中,每當(dāng)一個(gè)Fiber節(jié)點(diǎn)的flags
字段不為NoFlags
時(shí)(代表需要執(zhí)行副作用),就把該Fiber節(jié)點(diǎn)添加到EffectList,在Fiber樹構(gòu)建完成后,由Fiber節(jié)點(diǎn)串成的彩燈也構(gòu)建完成了,這樣僅僅需要遍歷彩燈就行了。
EffectList的收集
EffectList是一個(gè)單向鏈表,firstEffect
代表鏈表中的第一個(gè)Fiber節(jié)點(diǎn),lastEffect
代表鏈表中的最后一個(gè)Fiber節(jié)點(diǎn)。
Fiber樹的構(gòu)建是深度優(yōu)先的,也就是先向下構(gòu)建子級(jí)Fiber節(jié)點(diǎn),子級(jí)節(jié)點(diǎn)構(gòu)建完成后,再向上構(gòu)建父級(jí)Fiber節(jié)點(diǎn),所以EffectList中總是子級(jí)Fiber節(jié)點(diǎn)在前面。
Fiber節(jié)點(diǎn)構(gòu)建完成的操作執(zhí)行在completeUnitOfWork
方法,在這個(gè)方法里,不僅會(huì)對(duì)節(jié)點(diǎn)完成構(gòu)建,也會(huì)將有flags
的Fiber節(jié)點(diǎn)添加到EffectList。
簡(jiǎn)化代碼如下。
function completeUnitOfWork(unitOfWork: Fiber): void { let completedWork = unitOfWork; do { const current = completedWork.alternate; const returnFiber = completedWork.return; let next= completeWork(current, completedWork, subtreeRenderLanes); // effect list構(gòu)建 if ( returnFiber !== null && (returnFiber.flags & Incomplete) === NoFlags ) { // 層層拷貝 if (returnFiber.firstEffect === null) { returnFiber.firstEffect = completedWork.firstEffect; } if (completedWork.lastEffect !== null) { // 說(shuō)明當(dāng)前節(jié)點(diǎn)是兄弟節(jié)點(diǎn),子節(jié)點(diǎn)有effect,已經(jīng)給returnFiber.lastEffect賦值過(guò)了 if (returnFiber.lastEffect !== null) { // 連接兄弟節(jié)點(diǎn)的effect returnFiber.lastEffect.nextEffect = completedWork.firstEffect; } returnFiber.lastEffect = completedWork.lastEffect; } const flags = completedWork.flags; // 該fiber節(jié)點(diǎn)有effect if (flags > PerformedWork) { // 當(dāng)前節(jié)點(diǎn)有effect連接上effect list if (returnFiber.lastEffect !== null) { returnFiber.lastEffect.nextEffect = completedWork; } else { // returnFiber沒(méi)有firstEffect的情況是第一次遇見(jiàn)有effect的節(jié)點(diǎn) returnFiber.firstEffect = completedWork; } returnFiber.lastEffect = completedWork; } } // 兄弟元素遍歷再到返返回父級(jí) const siblingFiber = completedWork.sibling; if (siblingFiber !== null) { workInProgress = siblingFiber; return; } completedWork = returnFiber; workInProgress = completedWork; } while (completedWork !== null); }
EffectList實(shí)際是像冒泡一樣,一層一層不斷向上層收集,從第一個(gè)有flags
的節(jié)點(diǎn)開始記錄,每層的新節(jié)點(diǎn)都會(huì)將上一個(gè)節(jié)點(diǎn)的firstEffect
和lastEffect
拷貝到自身身上,再供上層節(jié)點(diǎn)再次拷貝。
如以下結(jié)構(gòu),假如每一個(gè)div
都有flags
。
<div id="1"> <div id="4"/> <div id="2"> <div id="3"/> </div> </div>
最終形成的EffectList為
firstEffect => div4 lastEffect => div1
因?yàn)镕iber樹的構(gòu)建深度優(yōu)先,所有div4
先完成completeWork
,構(gòu)建firstEffect
。
EffectList遍歷是從firstEffect
開始,通過(guò)每一個(gè)節(jié)點(diǎn)的nextEffect
找到下一個(gè)節(jié)點(diǎn)。
firstEffect => div4 div4.nextEffect => div3 div3.nextEffect => div2 div2.nextEffect => div1
初次Render時(shí)的EffectList
在React中,會(huì)對(duì)初次Mount有一個(gè)性能優(yōu)化,其中的Fiber節(jié)點(diǎn)的flags
不會(huì)包含placement
,對(duì)應(yīng)的DOM節(jié)點(diǎn)不會(huì)遍歷加入DOM樹,而是在創(chuàng)建DOM節(jié)點(diǎn)時(shí)就已經(jīng)加入DOM樹了,只有rootFiber節(jié)點(diǎn)FiberRootNode
的flags
會(huì)包含placement
。
EffectList是不會(huì)包含root
節(jié)點(diǎn)的,所以需要將root
節(jié)點(diǎn)也添加到EffectList,這樣才會(huì)正確的執(zhí)行placement
,讓DOM樹在頁(yè)面呈現(xiàn) 。
let firstEffect; // 把根節(jié)點(diǎn)finishedWork也連接進(jìn)去 if (finishedWork.flags > PerformedWork) { if (finishedWork.lastEffect !== null) { finishedWork.lastEffect.nextEffect = finishedWork; firstEffect = finishedWork.firstEffect; } else { firstEffect = finishedWork; } } else { // 根節(jié)點(diǎn)沒(méi)有effect. firstEffect = finishedWork.firstEffect; }
EffectList的遍歷
EffectList的主要是用于Layout階段生命周期方法的執(zhí)行和DOM的操作。
// 處理getSnapshotBeforeUpdate,調(diào)度useEffect nextEffect = firstEffect; do { commitBeforeMutationEffects(); } while (nextEffect !== null); // DOM操作 nextEffect = firstEffect; do { commitMutationEffects(root, renderPriorityLevel); } while (nextEffect !== null); // 生命周期方法的執(zhí)行 nextEffect = firstEffect; do { commitLayoutEffects(root, lanes); } while (nextEffect !== null);
在這Layout階段的這3個(gè)方法里,會(huì)遍歷nextEffect
,每執(zhí)行完一個(gè),就重新指向firstEffect
。Layout階段具體操作就不細(xì)講了。
總結(jié)
EffectList不是全局變量,只是在Fiber樹創(chuàng)建過(guò)程中,一層層向上收集有effect
的Fiber節(jié)點(diǎn),最終的root
節(jié)點(diǎn)就會(huì)收集到所有有effect
到Fiber節(jié)點(diǎn),我們就把這條包含effect
節(jié)點(diǎn)的鏈表叫做EffectList。
由于收集的過(guò)程是深度優(yōu)先,子級(jí)會(huì)先被收集,所以遍歷的時(shí)候也會(huì)先操作子級(jí),所以如果有面試官問(wèn)子級(jí)和父級(jí)的生命周期或者useEffect
誰(shuí)先執(zhí)行,就很清楚的知道會(huì)先執(zhí)行子級(jí)操作了。
以上就是簡(jiǎn)單分析React中的EffectList的詳細(xì)內(nèi)容,更多關(guān)于React中的EffectList的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- 使用hooks寫React組件需要注意的5個(gè)地方
- React+Ant Design開發(fā)環(huán)境搭建的實(shí)現(xiàn)步驟
- Vite搭建React項(xiàng)目的方法步驟
- react獲取input輸入框的值的方法示例
- react實(shí)現(xiàn)Radio組件的示例代碼
- React Router 如何使用history跳轉(zhuǎn)的實(shí)現(xiàn)
- 聊一聊我對(duì) React Context 的理解以及應(yīng)用
- react hooks入門詳細(xì)教程
- 使用 React 和 Threejs 創(chuàng)建一個(gè)VR全景項(xiàng)目的過(guò)程詳解
相關(guān)文章
從零開始學(xué)習(xí)搭建React腳手架項(xiàng)目
這篇文章主要介紹了從零開始學(xué)習(xí)搭建React腳手架項(xiàng)目,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-08-08React Native使用fetch實(shí)現(xiàn)圖片上傳的示例代碼
本篇文章主要介紹了React Native使用fetch實(shí)現(xiàn)圖片上傳的示例代碼,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03使用React實(shí)現(xiàn)內(nèi)容滑動(dòng)組件效果
這篇文章主要介紹了使用React實(shí)現(xiàn)一個(gè)內(nèi)容滑動(dòng)組件效果,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05React利用scheduler思想實(shí)現(xiàn)任務(wù)的打斷與恢復(fù)
這篇文章主要為大家詳細(xì)介紹了React如何利用scheduler思想實(shí)現(xiàn)任務(wù)的打斷與恢復(fù),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以參考一下2024-03-03TypeScript在React中的應(yīng)用技術(shù)實(shí)例解析
這篇文章主要為大家介紹了TypeScript在React中的應(yīng)用技術(shù)實(shí)例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-04-04React跨端動(dòng)態(tài)化之從JS引擎到RN落地詳解
這篇文章主要為大家介紹了React跨端動(dòng)態(tài)化之從JS引擎到RN落地,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09react16.8.0以上MobX在hook中的使用方法詳解
這篇文章主要為大家介紹了react16.8.0以上MobX在hook中的使用方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-07-07React-View-UI組件庫(kù)封裝Loading加載中源碼
這篇文章主要介紹了React-View-UI組件庫(kù)封裝Loading加載樣式,主要包括組件介紹,組件源碼及組件測(cè)試源碼,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-06-06