本文對應的 react
版本是 18.2.0
下面的 dom
結構react
內部是如何遍歷的
const App = () => {
return (
<div>
<button>+1</button>
<A count={0} />
</div>
);
};
const A = (props) => {
useEffect(() => {
console.log(props.count);
}, [props.count]);
return <div>{props.count}</div>;
};
react
內部遍歷核心邏輯:
- 在
render
時呼叫commitPassiveUnmountOnFiber
函式 commitPassiveUnmountOnFiber
處理不同的WorkTag
,並呼叫recursivelyTraversePassiveUnmountEffects
recursivelyTraversePassiveUnmountEffects
根據當前Fiber
的子節點有沒有passive effect
(useEffect
,useLayoutEffect
)來決定是否遍歷當前Fiber
的子節點- 如果子節點有
passive effect
,則優先遍歷子節點 (深度優先),直到找到最終的葉子節點,退出當前迴圈 然後進入兄弟節點,開始遍歷兄弟節點的子節點
- 具體從哪個兄弟節點開始遍歷,
react
選擇的是離退出迴圈的那個葉子節點的父節點,檢查有沒有子節點,以此迴圈遍歷
- 具體從哪個兄弟節點開始遍歷,
- 直到最後找到所有有
passive effect
的節點
- 如果子節點有
commitPassiveUnmountOnFiber(root.current);
function commitPassiveUnmountOnFiber(finishedWork) {
// 省略了處理不同的 WorkTag
recursivelyTraversePassiveUnmountEffects(finishedWork);
}
function recursivelyTraversePassiveUnmountEffects(parentFiber) {
// 省略了其他處理
if (parentFiber.subtreeFlags & PassiveMask) {
let child = parentFiber.child;
while (child !== null) {
commitPassiveUnmountOnFiber(child);
child = child.sibling;
}
}
}
所以對於這段 dom
的遍歷邏輯是:
首先從根元件開始
FiberRootNode
,取到current
- 也就是說
FiberRootNode.current
是div#root
這是一個fiber
,它的tag
是3
- 也就是說
- 由於
App
的子元件有passive effect
,所以會進入App
元件,它的tag
是0
App
元件中節點是<div>
,<di >
的tag
是5
<div>
下面有兩個子元素<button>
、<A>
- 先遍歷
<button>
它的tag
是5
<button>
內部只有一個文字節點,沒有passive effect
- 所以
react
不遍歷了(跳出當前遍歷的迴圈,也就是button
這條不在遍歷了)
- 所以
- 跳出迴圈後,檢視
button
的兄弟節點,它的兄弟節點是<A>
,<A>
的tag
是0
- 由於
<A>
節點的子節點沒有passive effect
,所以跳出迴圈,結束整個遍歷
總結
- 從跟節點開始遍歷
- 當前元件的子元件有沒有
passive effect
- 採取深度優先
- 如果
dom
節點內有函式元件,則這個dom
會被遍歷,否則不會遍歷 - 如果當前
fiber
下的所有子fiber
都沒有passive effect
,則這一整個都連結串列都不會被遍歷 - 如果當前
fiber
只有dom
,則這些dom
也不會遍歷
總的來說元件會不會別遍歷看 fiber
有沒有 passive effect
:
- 有,一定會被遍歷
沒有,下面兩種情況會被遍歷,其他情況不會被遍歷
- 是
passive effect
的父元件 - 和
passive effect
元件是兄弟元件
- 是
passive effect
指的是 useEffect
,useLayoutEffect
遍歷邏輯如下圖所示
圖中畫綠色勾的都會被遍歷,紅色勾是遍歷的順序
往期文章
更多 react
原始碼文章