我的编程空间,编程开发者的网络收藏夹
学习永远不晚

useEffect 返回函数执行过程源码解析

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

useEffect 返回函数执行过程源码解析

引言

本文对应的 react 版本是 18.2.0

在掌握 React 组件树遍历技巧中说到 react 是怎么遍历 dom

那么在遍历的过程中,如果发现当前节点有子节点被删除了,那么 react 会怎么处理呢?

下面是源码简化:这里是完整的源码

function recursivelyTraversePassiveUnmountEffects(parentFiber: Fiber): void {
  const deletions = parentFiber.deletions;
  if ((parentFiber.flags & ChildDeletion) !== NoFlags) {
    if (deletions !== null) {
      for (let i = 0; i < deletions.length; i++) {
        const childToDelete = deletions[i];
        nextEffect = childToDelete;
        commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
          childToDelete,
          parentFiber
        );
      }
    }
  }
}
function commitPassiveUnmountEffectsInsideOfDeletedTree_begin(
  deletedSubtreeRoot: Fiber,
  nearestMountedAncestor: Fiber | null
) {
  while (nextEffect !== null) {
    const fiber = nextEffect;
    // 执行 passive effects 返回的函数
    commitPassiveUnmountInsideDeletedTreeOnFiber(fiber, nearestMountedAncestor);
    const child = fiber.child;
    if (child !== null) {
      child.return = fiber;
      nextEffect = child;
    } else {
      commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
        deletedSubtreeRoot
      );
    }
  }
}
function commitPassiveUnmountEffectsInsideOfDeletedTree_complete(
  deletedSubtreeRoot
) {
  while (nextEffect !== null) {
    const fiber = nextEffect;
    const sibling = fiber.sibling;
    const returnFiber = fiber.return;
    if (fiber === deletedSubtreeRoot) {
      nextEffect = null;
      return;
    }
    if (sibling !== null) {
      sibling.return = returnFiber;
      nextEffect = sibling;
      return;
    }
    nextEffect = returnFiber;
  }
}

deletions

在正式开始之前,我们要了解一个 fiber 的属性:deletions

这个属性存放的是当前节点中被删除的 fiber,这个数组是在 commit 阶段被赋值的

如果有被删除的节点,这个属性值是一个数组,如果没有被删除的节点,这个属性值是 null

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

如果 App 组件这样写,那么 deletions 的值是 [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>
}

如果 App 组件这样写,那么 deletions 的值是 [FiberNode]

const App(){
  const [count, setCount] = useState(0)
  return <div>
    {count % 2 === 0 && <><A /><B /></>}
    <div onClick={()=> setCount(count+1)}>+1</div>
  </div>
}

对于第二种情况,react 会把 A 组件和 B 组件作为一个整体,所以 deletions 的值是 [FiberNode]

处理当前节点的 deletions

react 在遍历 fiber tree 时,会先处理当前的 fiberdeletions,等处理完之后再遍历下一个 fiber

现在我们已经知道 deletions 中保存的是当前 fiber 下被删除的子节点

这时 react 会遍历 deletions 数组,然后执行每个 fiberpassive effect 返回的函数

但是有个问题,如果 deletions 中的 fiber 有子节点,那么这些子节点也会被删除,这时 react 会怎么处理呢?

这里分两种情况来讨论:

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

删除的 fiber 没有子节点:

<div>{xxxx && <A />}</div>

这种情况比较好理解

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

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

如下图所示:

删除的 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 返回的函数

如下图所示:

向下遍历和向上遍历

在处理 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 过程

以上就是useEffect 返回函数执行过程源码解析的详细内容,更多关于useEffect 返回函数执行的资料请关注编程网其它相关文章!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

useEffect 返回函数执行过程源码解析

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

useEffect 返回函数执行过程源码解析

这篇文章主要为大家介绍了useEffect 返回函数执行过程源码解析,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2023-05-16

useEffect返回函数执行过程是什么

本文小编为大家详细介绍“useEffect返回函数执行过程是什么”,内容详细,步骤清晰,细节处理妥当,希望这篇“useEffect返回函数执行过程是什么”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。下面是源码简化
2023-07-06

Spring@Bean注解深入分析源码执行过程

随着SpringBoot的流行,我们现在更多采用基于注解式的配置从而替换掉了基于XML的配置,所以本篇文章我们主要探讨基于注解的@Bean以及和其他注解的使用
2023-01-10

【Mybatis源码解析】mapper实例化及执行流程源码分析

文章目录 简介 环境搭建 源码解析 附 基础环境:JDK17、SpringBoot3.0、mysql5.7 储备知识:《【Spring6源码・AOP】AOP源码解析》、《JDBC详细
2023-08-20

Django执行源生mysql语句实现过程解析

1.使用extra方法解释:结果集修改器,一种提供额外查询参数的机制说明:依赖model模型 使用方式:用在where后:Book.objects.filter(publisher_id="1").extra(where=["title='
2022-05-11

Java从源码角度解析SpringMVC执行流程

这篇文章主要介绍了Java从源码角度解析SpringMVC执行流程,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
2023-05-16

ThreadPoolExecutor参数含义及源码执行流程详解

这篇文章主要为大家介绍了ThreadPoolExecutor参数含义及源码执行流程详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
2022-11-13

深入解析golang编译器的编译过程:从源码到可执行文件

从源码到可执行文件:解析golang编译器的编译过程概述:Golang是一种快速、简单和可靠的编程语言,而其编译器是将Golang代码转换为可执行文件的关键工具。在这篇文章中,我们将深入探究Golang编译器的编译过程,从源码到最终生成的可
深入解析golang编译器的编译过程:从源码到可执行文件
2023-12-29

C++ 函数调试详解:如何深入了解函数的执行过程?

c++++ 函数调试的关键技能包括:1. 设置断点以暂停执行;2. 单步执行逐行查看代码;3. 检查变量监视值;4. 打印调试信息查看特定状态。通过实战案例,可以深入了解函数执行过程,快速找出并修复错误。C++ 函数调试详解:深入函数执行过
C++ 函数调试详解:如何深入了解函数的执行过程?
2024-05-03

编程热搜

目录