useEffect?返回函數(shù)執(zhí)行過程源碼解析
引言
本文對應(yīng)的 react
版本是 18.2.0
在掌握 React 組件樹遍歷技巧中說到 react
是怎么遍歷 dom
那么在遍歷的過程中,如果發(fā)現(xiàn)當(dāng)前節(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
這個屬性存放的是當(dāng)前節(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]
處理當(dāng)前節(jié)點的 deletions
react
在遍歷 fiber tree
時,會先處理當(dāng)前的 fiber
的 deletions
,等處理完之后再遍歷下一個 fiber
現(xiàn)在我們已經(jīng)知道 deletions
中保存的是當(dāng)前 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>
這種情況比較好理解
當(dāng)遍歷到 div
時,因為 <A/>
節(jié)點會被卸載,所以在 div
的 deletions
保存了一個 <A/>
的 fiber
遍歷 deletions
數(shù)組,執(zhí)行 <A/>
的 passive effect
返回的函數(shù)
如下圖所示:
刪除的 fiber 有子節(jié)點:
<div>{xxxx && <><A /><B /></>}</div>
這種情況就比較復(fù)雜了
當(dāng)遍歷到 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
,也就是說從當(dāng)前的 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é)點)
總結(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
,結(jié)束遍歷
2. 結(jié)合掌握 React 組件樹遍歷技巧
- 遍歷尋找有
passive effect
節(jié)點react
從根組件向下遍歷,如果沒有passive effect
,則不會遍歷
- 遍歷時,如果遇到當(dāng)前節(jié)點有
deletions
時,會暫停尋找passive effect
節(jié)點- 進入遍歷
deletions
數(shù)組
- 進入遍歷
react 遍歷 deletions 完整邏輯如下圖所示:
圖中綠色部分是遍歷 deletionsNode
過程,紅色部分是遍歷尋找 passive effect
過程
以上就是useEffect 返回函數(shù)執(zhí)行過程源碼解析的詳細(xì)內(nèi)容,更多關(guān)于useEffect 返回函數(shù)執(zhí)行的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
一起來學(xué)習(xí)React元素的創(chuàng)建和渲染
這篇文章主要為大家詳細(xì)介紹了React元素的創(chuàng)建和渲染,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助2022-03-03解決React報錯React.Children.only expected to rece
這篇文章主要為大家介紹了React報錯React.Children.only expected to receive single React element child分析解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-01-01React?createRef循環(huán)動態(tài)賦值ref問題
這篇文章主要介紹了React?createRef循環(huán)動態(tài)賦值ref問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-01-01淺談React Native 傳參的幾種方式(小結(jié))
這篇文章主要介紹了淺談React Native 傳參的幾種方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05