头图

本文对应的 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 内部遍历核心逻辑:

  1. render 时调用 commitPassiveUnmountOnFiber 函数
  2. commitPassiveUnmountOnFiber 处理不同的 WorkTag,并调用 recursivelyTraversePassiveUnmountEffects
  3. recursivelyTraversePassiveUnmountEffects 根据当前 Fiber 的子节点有没有 passive effectuseEffectuseLayoutEffect)来决定是否遍历当前 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 的遍历逻辑是:

  1. 首先从根组件开始 FiberRootNode,取到 current

    • 也就是说 FiberRootNode.currentdiv#root 这是一个 fiber,它的 tag3
  2. 由于 App 的子组件有 passive effect,所以会进入 App 组件,它的 tag0
  3. App 组件中节点是 <div><di >tag5

    • <div> 下面有两个子元素 <button><A>
  4. 先遍历 <button> 它的 tag5
  5. <button> 内部只有一个文本节点,没有 passive effect

    • 所以 react 不遍历了(跳出当前遍历的循环,也就是 button 这条不在遍历了)
  6. 跳出循环后,查看 button 的兄弟节点,它的兄弟节点是 <A><A>tag0
  7. 由于 <A> 节点的子节点没有 passive effect,所以跳出循环,结束整个遍历

总结

  1. 从跟节点开始遍历
  2. 当前组件的子组件有没有 passive effect
  3. 采取深度优先
  4. 如果 dom 节点内有函数组件,则这个 dom 会被遍历,否则不会遍历
  5. 如果当前 fiber 下的所有子 fiber 都没有 passive effect ,则这一整个都链表都不会被遍历
  6. 如果当前 fiber 只有 dom,则这些 dom 也不会遍历

总的来说组件会不会别遍历看 fiber 有没有 passive effect

  • 有,一定会被遍历
  • 没有,下面两种情况会被遍历,其他情况不会被遍历

    • passive effect 的父组件
    • passive effect 组件是兄弟组件

passive effect 指的是 useEffectuseLayoutEffect

遍历逻辑如下图所示

图中画绿色勾的都会被遍历,红色勾是遍历的顺序

react遍历逻辑

往期文章

  1. 深入探究 React 原生事件的工作原理
  2. React Lane 算法:一文详解 8 种 Lane 操作
  3. 剖析 React 任务调度机制:scheduleCallback 实现原理

更多 react 源码文章


uccs
756 声望88 粉丝

3年 gis 开发,wx:ttxbg210604