乐趣区

关于javascript:掌握-React-组件树遍历技巧

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

遍历逻辑如下图所示

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

往期文章

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

更多 react 源码文章

退出移动版