React中,會遍歷EffectList來執行節點操作、生命周期方法、Effect方法,可以把EffectList比作圣誕樹上掛的彩燈,而這顆圣誕樹就是Fiber樹。
為什么會存在EffectList呢?打個比方來說,一顆Fiber樹中有一些Fiber節點需要執行componentDidMount
方法,如果在Fiber樹構建完成后,再遍歷一次Fiber樹,找到需要執行componentDidMount
方法的Fiber節點,這是非常低效的。
而EffectList就解決了這個問題,在Fiber樹構建過程中,每當一個Fiber節點的flags
字段不為NoFlags
時(代表需要執行副作用),就把該Fiber節點添加到EffectList,在Fiber樹構建完成后,由Fiber節點串成的彩燈也構建完成了,這樣僅僅需要遍歷彩燈就行了。
EffectList的收集
EffectList是一個單向鏈表,firstEffect
代表鏈表中的第一個Fiber節點,lastEffect
代表鏈表中的最后一個Fiber節點。
Fiber樹的構建是深度優先的,也就是先向下構建子級Fiber節點,子級節點構建完成后,再向上構建父級Fiber節點,所以EffectList中總是子級Fiber節點在前面。
Fiber節點構建完成的操作執行在completeUnitOfWork
方法,在這個方法里,不僅會對節點完成構建,也會將有flags
的Fiber節點添加到EffectList。
簡化代碼如下。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
|
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構建 if ( returnFiber !== null && (returnFiber.flags & Incomplete) === NoFlags ) { // 層層拷貝 if (returnFiber.firstEffect === null ) { returnFiber.firstEffect = completedWork.firstEffect; } if (completedWork.lastEffect !== null ) { // 說明當前節點是兄弟節點,子節點有effect,已經給returnFiber.lastEffect賦值過了 if (returnFiber.lastEffect !== null ) { // 連接兄弟節點的effect returnFiber.lastEffect.nextEffect = completedWork.firstEffect; } returnFiber.lastEffect = completedWork.lastEffect; } const flags = completedWork.flags; // 該fiber節點有effect if (flags > PerformedWork) { // 當前節點有effect連接上effect list if (returnFiber.lastEffect !== null ) { returnFiber.lastEffect.nextEffect = completedWork; } else { // returnFiber沒有firstEffect的情況是第一次遇見有effect的節點 returnFiber.firstEffect = completedWork; } returnFiber.lastEffect = completedWork; } } // 兄弟元素遍歷再到返返回父級 const siblingFiber = completedWork.sibling; if (siblingFiber !== null ) { workInProgress = siblingFiber; return ; } completedWork = returnFiber; workInProgress = completedWork; } while (completedWork !== null ); } |
EffectList實際是像冒泡一樣,一層一層不斷向上層收集,從第一個有flags
的節點開始記錄,每層的新節點都會將上一個節點的firstEffect
和lastEffect
拷貝到自身身上,再供上層節點再次拷貝。
如以下結構,假如每一個div
都有flags
。
1
2
3
4
5
6
|
< div id = "1" > < div id = "4" /> < div id = "2" > < div id = "3" /> </ div > </ div > |
最終形成的EffectList為
1
2
|
firstEffect => div4 lastEffect => div1 |
因為Fiber樹的構建深度優先,所有div4
先完成completeWork
,構建firstEffect
。
EffectList遍歷是從firstEffect
開始,通過每一個節點的nextEffect
找到下一個節點。
1
2
3
4
|
firstEffect => div4 div4.nextEffect => div3 div3.nextEffect => div2 div2.nextEffect => div1 |
初次Render時的EffectList
在React中,會對初次Mount有一個性能優化,其中的Fiber節點的flags
不會包含placement
,對應的DOM節點不會遍歷加入DOM樹,而是在創建DOM節點時就已經加入DOM樹了,只有rootFiber節點FiberRootNode
的flags
會包含placement
。
EffectList是不會包含root
節點的,所以需要將root
節點也添加到EffectList,這樣才會正確的執行placement
,讓DOM樹在頁面呈現 。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
let firstEffect; // 把根節點finishedWork也連接進去 if (finishedWork.flags > PerformedWork) { if (finishedWork.lastEffect !== null ) { finishedWork.lastEffect.nextEffect = finishedWork; firstEffect = finishedWork.firstEffect; } else { firstEffect = finishedWork; } } else { // 根節點沒有effect. firstEffect = finishedWork.firstEffect; } |
EffectList的遍歷
EffectList的主要是用于Layout階段生命周期方法的執行和DOM的操作。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// 處理getSnapshotBeforeUpdate,調度useEffect nextEffect = firstEffect; do { commitBeforeMutationEffects(); } while (nextEffect !== null ); // DOM操作 nextEffect = firstEffect; do { commitMutationEffects(root, renderPriorityLevel); } while (nextEffect !== null ); // 生命周期方法的執行 nextEffect = firstEffect; do { commitLayoutEffects(root, lanes); } while (nextEffect !== null ); |
在這Layout階段的這3個方法里,會遍歷nextEffect
,每執行完一個,就重新指向firstEffect
。Layout階段具體操作就不細講了。
總結
EffectList不是全局變量,只是在Fiber樹創建過程中,一層層向上收集有effect
的Fiber節點,最終的root
節點就會收集到所有有effect
到Fiber節點,我們就把這條包含effect
節點的鏈表叫做EffectList。
由于收集的過程是深度優先,子級會先被收集,所以遍歷的時候也會先操作子級,所以如果有面試官問子級和父級的生命周期或者useEffect
誰先執行,就很清楚的知道會先執行子級操作了。
以上就是簡單分析React中的EffectList的詳細內容,更多關于React中的EffectList的資料請關注服務器之家其它相關文章!
原文鏈接:https://juejin.cn/post/6947168516394975239