本文对应的 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
源码文章