useEffect?返回函數(shù)執(zhí)行過程源碼解析
引言
本文對應的 react 版本是 18.2.0
在掌握 React 組件樹遍歷技巧中說到 react 是怎么遍歷 dom
那么在遍歷的過程中,如果發(fā)現(xiàn)當前節(jié)點有子節(jié)點被刪除了,那么 react 會怎么處理呢?
下面是源碼簡化:這里是完整的源碼
function recursivelyTraversePassiveUnmountEffects(parentFiber: Fiber): void {
const deletions = parentFiber.deletions;
if ((parentFiber.flags & ChildDeletion) !== NoFlags) {
if (deletions !== null) {
for (let i = 0; i < deletions.length; i++) {
const childToDelete = deletions[i];
nextEffect = childToDelete;
commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
childToDelete,
parentFiber
);
}
}
}
}
function commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
deletedSubtreeRoot: Fiber,
nearestMountedAncestor: Fiber | null
) {
while (nextEffect !== null) {
const fiber = nextEffect;
// 執(zhí)行 passive effects 返回的函數(shù)
commitPassiveUnmountInsideDeletedTreeOnFiber(fiber, nearestMountedAncestor);
const child = fiber.child;
if (child !== null) {
child.return = fiber;
nextEffect = child;
} else {
commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
deletedSubtreeRoot
);
}
}
}
function commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
deletedSubtreeRoot
) {
while (nextEffect !== null) {
const fiber = nextEffect;
const sibling = fiber.sibling;
const returnFiber = fiber.return;
if (fiber === deletedSubtreeRoot) {
nextEffect = null;
return;
}
if (sibling !== null) {
sibling.return = returnFiber;
nextEffect = sibling;
return;
}
nextEffect = returnFiber;
}
}
deletions
在正式開始之前,我們要了解一個 fiber 的屬性:deletions
這個屬性存放的是當前節(jié)點中被刪除的 fiber,這個數(shù)組是在 commit 階段被賦值的
如果有被刪除的節(jié)點,這個屬性值是一個數(shù)組,如果沒有被刪除的節(jié)點,這個屬性值是 null
const A = () => {
useEffect(() => {
return () => {
console.log("A unmount");
};
}, []);
return <div>文本A</div>;
};
const B = () => {
useEffect(() => {
return () => {
console.log("B unmount");
};
}, []);
return <div>文本B</div>;
};
如果 App 組件這樣寫,那么 deletions 的值是 [FiberNode, FiberNode]
const App(){
const [count, setCount] = useState(0)
return <div>
{count % 2 === 0 && <A />}
{count % 2 === 0 && <B />}
<div onClick={()=> setCount(count+1)}>+1</div>
</div>
}
如果 App 組件這樣寫,那么 deletions 的值是 [FiberNode]
const App(){
const [count, setCount] = useState(0)
return <div>
{count % 2 === 0 && <><A /><B /></>}
<div onClick={()=> setCount(count+1)}>+1</div>
</div>
}
對于第二種情況,react 會把 A 組件和 B 組件作為一個整體,所以 deletions 的值是 [FiberNode]
處理當前節(jié)點的 deletions
react 在遍歷 fiber tree 時,會先處理當前的 fiber 的 deletions,等處理完之后再遍歷下一個 fiber
現(xiàn)在我們已經(jīng)知道 deletions 中保存的是當前 fiber 下被刪除的子節(jié)點
這時 react 會遍歷 deletions 數(shù)組,然后執(zhí)行每個 fiber 的 passive effect 返回的函數(shù)
但是有個問題,如果 deletions 中的 fiber 有子節(jié)點,那么這些子節(jié)點也會被刪除,這時 react 會怎么處理呢?
這里分兩種情況來討論:
- 刪除的
fiber沒有子節(jié)點:<div>{xxxx && <A />}</div> - 刪除的
fiber有子節(jié)點:<div>{xxxx && <><A /><B /></>}</div>-->
刪除的 fiber 沒有子節(jié)點:
<div>{xxxx && <A />}</div>
這種情況比較好理解
當遍歷到 div 時,因為 <A/> 節(jié)點會被卸載,所以在 div 的 deletions 保存了一個 <A/> 的 fiber
遍歷 deletions 數(shù)組,執(zhí)行 <A/> 的 passive effect 返回的函數(shù)
如下圖所示:

刪除的 fiber 有子節(jié)點:
<div>{xxxx && <><A /><B /></>}</div>
這種情況就比較復雜了
當遍歷到 div 時,<></> 節(jié)點會被卸載,所以在 div 的 deletions 保存了一個 <></> 的 fiber
遍歷 deletions 數(shù)組,執(zhí)行 fiber 的 passive effect 返回的函數(shù),對于 <></> 來說是不存在的 passive effect
那么這個時候就要去遍歷它的 child.fiber,也就是 <A/> 和 <B/>
首先拿到第一個 fiber,也就是 <A/>,然后執(zhí)行 <A/> 的 passive effect 返回的函數(shù),這步比較好理解
child = fiber.child;
if (child !== null) {
nextEffect = child;
}
這里遍歷也是深度優(yōu)先,遍歷一個 child,執(zhí)行一個 passive effect 返回函數(shù),然后再遍歷下一個 child(這邊 <A /> 已經(jīng)是葉子節(jié)點了)
然后拿到第二個 fiber,也就是 <B/>,然后執(zhí)行 <B/> 的 passive effect 返回的函數(shù),這步就不太好理解了
child = fiber.child;
if (child !== null) {
nextEffect = child;
} else {
commitPassiveUnmountEffectsInsideOfDeletedTree_complete(deletedSubtreeRoot);
}
這里要注意的是:
react 在尋找有 passive effect 的 fiber 時,只遍歷到有 passive effect 的 fiber, 像 div 這種沒有 passive effect 就不會遍歷
但是在處理 deletions,react 會遍歷所有的 fiber,也就是說從當前的 fiber 開始,一直往下遍歷到葉子節(jié)點,這個葉子節(jié)點是指文本節(jié)點這種,往下不會有節(jié)點了(對于 A 組件來說 文本A 是文本節(jié)點)
然后在開始往上遍歷,往上遍歷是調(diào)用 commitPassiveUnmountEffectsInsideOfDeletedTree_complete 函數(shù),直到遍歷到 deletionRoot,在向上遍歷的過程中會檢查是否有 sibling,如果有說明 sibling 還沒被處理,這樣就找到了 <B/>,然后執(zhí)行 <B/> 的 passive effect 返回的函數(shù)
如下圖所示:

向下遍歷和向上遍歷
在處理 deletions 時,對于每個 deletedNode,都先向下遍歷,然后再向上遍歷
- 向下遍歷:
commitPassiveUnmountEffectsInsideOfDeletedTree_begin(深度優(yōu)先,優(yōu)先處理左邊的節(jié)點) - 向上遍歷:
commitPassiveUnmountEffectsInsideOfDeletedTree_complete(之后再處理右邊節(jié)點)
總結
1. 遍歷 deletions 數(shù)組:
react在處理deletions時,先沿著fiber tree向下遍歷,如果有passive effect返回的函數(shù),則執(zhí)行- 一直遍歷到?jīng)]有
child的fiber,再向上遍歷,處理sibling - 再向上遍歷時,如果如果遇到
sibling,再向下遍歷,向下遍歷時遇到passive effect返回的函數(shù),則執(zhí)行 - 如此循環(huán)直到遍歷到
deletedNode,結束遍歷
2. 結合掌握 React 組件樹遍歷技巧
- 遍歷尋找有
passive effect節(jié)點react從根組件向下遍歷,如果沒有passive effect,則不會遍歷
- 遍歷時,如果遇到當前節(jié)點有
deletions時,會暫停尋找passive effect節(jié)點- 進入遍歷
deletions數(shù)組
- 進入遍歷
react 遍歷 deletions 完整邏輯如下圖所示:
圖中綠色部分是遍歷 deletionsNode 過程,紅色部分是遍歷尋找 passive effect 過程

以上就是useEffect 返回函數(shù)執(zhí)行過程源碼解析的詳細內(nèi)容,更多關于useEffect 返回函數(shù)執(zhí)行的資料請關注腳本之家其它相關文章!
相關文章
React版本18.xx降低為17.xx的方法實現(xiàn)
由于現(xiàn)在react默認創(chuàng)建是18.xx版本,但是我們現(xiàn)在大多使用的還是17.xx或者更低的版本,于是要對react版本進行降級,本文主要介紹了React版本18.xx降低為17.xx的方法實現(xiàn),感興趣的可以了解一下2023-11-11
如何應用?SOLID?原則在?React?中整理代碼之開閉原則
React?不是面向對象,但這些原則背后的主要思想可能是有幫助的,在本文中,我將嘗試演示如何應用這些原則來編寫更好的代碼,對React?SOLID原則開閉原則相關知識感興趣的朋友一起看看吧2022-07-07
React Hooks之使用useCallback和useMemo進行性能優(yōu)化方式
這篇文章主要介紹了React Hooks之使用useCallback和useMemo進行性能優(yōu)化方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-06-06
淺談react-native熱更新react-native-pushy集成遇到的問題
下面小編就為大家?guī)硪黄獪\談react-native熱更新react-native-pushy集成遇到的問題。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2017-09-09

