簡(jiǎn)單分析React中的EffectList
React中,會(huì)遍歷EffectList來執(zhí)行節(jié)點(diǎn)操作、生命周期方法、Effect方法,可以把EffectList比作圣誕樹上掛的彩燈,而這顆圣誕樹就是Fiber樹。
為什么會(huì)存在EffectList呢?打個(gè)比方來說,一顆Fiber樹中有一些Fiber節(jié)點(diǎn)需要執(zhí)行componentDidMount方法,如果在Fiber樹構(gòu)建完成后,再遍歷一次Fiber樹,找到需要執(zhí)行componentDidMount方法的Fiber節(jié)點(diǎn),這是非常低效的。
而EffectList就解決了這個(gè)問題,在Fiber樹構(gòu)建過程中,每當(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) {
// 說明當(dāng)前節(jié)點(diǎn)是兄弟節(jié)點(diǎn),子節(jié)點(diǎn)有effect,已經(jīng)給returnFiber.lastEffect賦值過了
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沒有firstEffect的情況是第一次遇見有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開始,通過每一個(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樹在頁面呈現(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)沒有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)建過程中,一層層向上收集有effect的Fiber節(jié)點(diǎn),最終的root節(jié)點(diǎn)就會(huì)收集到所有有effect到Fiber節(jié)點(diǎn),我們就把這條包含effect節(jié)點(diǎn)的鏈表叫做EffectList。
由于收集的過程是深度優(yōu)先,子級(jí)會(huì)先被收集,所以遍歷的時(shí)候也會(huì)先操作子級(jí),所以如果有面試官問子級(jí)和父級(jí)的生命周期或者useEffect誰先執(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ān)文章
react中通過props實(shí)現(xiàn)父子組件間通信的使用示例
在React中,父組件可以通過props屬性向子組件傳遞數(shù)據(jù),子組件可以通過props屬性接收父組件傳遞過來的數(shù)據(jù),本文就來介紹一下如何實(shí)現(xiàn),感興趣的可以了解一下2023-10-10
使用react render props實(shí)現(xiàn)倒計(jì)時(shí)的示例代碼
這篇文章主要介紹了使用react render props實(shí)現(xiàn)倒計(jì)時(shí)的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-12-12
React中useState原理的代碼簡(jiǎn)單實(shí)現(xiàn)
要實(shí)現(xiàn)useState的背后原理,則需要深入了解狀態(tài)是如何在函數(shù)組件的渲染周期中保持和更新的,本文將通過一段代碼簡(jiǎn)單闡述useState鉤子函數(shù)的實(shí)現(xiàn)思路,希望對(duì)大家有所幫助2023-12-12

