How is the function returned by useEffect executed?

The corresponding reactversion is18.2.0

Before officially starting, we need to understand the properties fiberof :deletions

This attribute stores the deleted ones in the current node fiber, and this array is assigned in committhe stage

If there is a deleted node, the property value is an array, if there is no deleted node, the property value isnull

const A = () => {
  useEffect(() => {
    return () => {
      console.log("A unmount");
    };
  }, []);
  return <div>文本A</div>;
};
const B = () => {
  useEffect(() => {
    return () => {
      console.log("B unmount");
    };
  }, []);
  return <div>文本B</div>;
};
复制代码

If Appthe component is written like this, then deletionsthe value of is[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>
}
复制代码

If Appthe component is written like this, then deletionsthe value of is[FiberNode]

const App(){
  const [count, setCount] = useState(0)

  return <div>
    {count % 2 === 0 && <><A /><B /></>}
    <div onClick={()=> setCount(count+1)}>+1</div>
  </div>
}
复制代码

For the second case, the componentreact and the component are considered as a whole, so the value of isABdeletions[FiberNode]

Handle deletions of the current node

reactfiber treeWhen traversing , the current fiberone will be processed first deletions, and then the next one will be traversed after processingfiber

Now we already know that what is saved deletionsin is fiberthe child node that is deleted under the current

At this time, reactit will traverse the array, and then execute the function returned bydeletions eachfiberpassive effect

But there is a problem, if deletionsin fiberhas child nodes, then these child nodes will also be deleted, how reactto ?

There are two situations to discuss here:

  1. Deleted fiberhas no children:<div>{xxxx && <A />}</div>
  2. The deleted fiberhas child nodes: <div>{xxxx && <><A /><B /></>}</div>-->

A deleted fiber has no children:<div>{xxxx && <A />}</div>

This situation is better understood

当遍历到 div 时,因为 <A/> 节点会被卸载,所以在 divdeletions 保存了一个 <A/>fiber

遍历 deletions 数组,执行 <A/>passive effect 返回的函数

如下图所示:

删除的fiber没有子节点.png

删除的 fiber 有子节点:<div>{xxxx && <><A /><B /></>}</div>

这种情况就比较复杂了

当遍历到 div 时,<></> 节点会被卸载,所以在 divdeletions 保存了一个 <></>fiber

遍历 deletions 数组,执行 fiberpassive effect 返回的函数,对于 <></> 来说是不存在的 passive effect

那么这个时候就要去遍历它的 child.fiber,也就是 <A/><B/>

首先拿到第一个 fiber,也就是 <A/>,然后执行 <A/>passive effect 返回的函数,这步比较好理解

child = fiber.child;
if (child !== null) {
  nextEffect = child;
}
复制代码

这里遍历也是深度优先,遍历一个 child,执行一个 passive effect 返回函数,然后再遍历下一个 child(这边 <A /> 已经是叶子节点了)

然后拿到第二个 fiber,也就是 <B/>,然后执行 <B/>passive effect 返回的函数,这步就不太好理解了

child = fiber.child;
if (child !== null) {
  nextEffect = child;
} else {
  commitPassiveUnmountEffectsInsideOfDeletedTree_complete(deletedSubtreeRoot);
}
复制代码

这里要注意的是:

react 在寻找有 passive effectfiber 时,只遍历到有 passive effectfiber, 像 div 这种没有 passive effect 就不会遍历

但是在处理 deletionsreact 会遍历所有的 fiber,也就是说从当前的 fiber 开始,一直往下遍历到叶子节点,这个叶子节点是指文本节点这种,往下不会有节点了(对于 A 组件来说 文本A 是文本节点)

然后在开始往上遍历,往上遍历是调用 commitPassiveUnmountEffectsInsideOfDeletedTree_complete 函数,直到遍历到 deletionRoot,在向上遍历的过程中会检查是否有 sibling,如果有说明 sibling 还没被处理,这样就找到了 <B/>,然后执行 <B/>passive effect 返回的函数

如下图所示:

删除的fiber有子节点.png

向下遍历和向上遍历

在处理 deletions 时,对于每个 deletedNode,都先向下遍历,然后再向上遍历

  • 向下遍历:commitPassiveUnmountEffectsInsideOfDeletedTree_begin(深度优先,优先处理左边的节点)
  • 向上遍历:commitPassiveUnmountEffectsInsideOfDeletedTree_complete(之后再处理右边节点)

总结

1. 遍历 deletions 数组:

  • react 在处理 deletions 时,先沿着 fiber tree 向下遍历,如果有 passive effect 返回的函数,则执行
  • 一直遍历到没有 childfiber,再向上遍历,处理 sibling
  • 再向上遍历时,如果如果遇到 sibling,再向下遍历,向下遍历时遇到 passive effect 返回的函数,则执行
  • 如此循环直到遍历到 deletedNode,结束遍历

2. 结合掌握 React 组件树遍历技巧

  • 遍历寻找有 passive effect 节点
    • react 从根组件向下遍历,如果没有 passive effect,则不会遍历
  • 遍历时,如果遇到当前节点有 deletions 时,会暂停寻找 passive effect 节点
    • 进入遍历 deletions 数组

react 遍历 deletions 完整逻辑如下图所示:

图中绿色部分是遍历 deletionsNode 过程,红色部分是遍历寻找 passive effect 过程

react执行deletions逻辑.png

往期文章

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

更多 react 源码文章

Guess you like

Origin juejin.im/post/7222549965272531001