React16源码: React中commitAllHostEffects内部的commitDeletion的源码实现

commitDeletion


1 )概述

  • 在 react commit 阶段的 commitRoot 第二个while循环中
  • 调用了 commitAllHostEffects,这个函数不仅仅处理了新增节点,更新节点
  • 最后一个操作,就是删除节点,就需要调用 commitDeletion,这里面做什么呢?
  • 遍历子树
    • 因为删除的一个节点,虽然它可能是一个dom节点(在react中是fiber对象)
    • 但对于react组件树来说,dom 节点(fiber对象)下面是可以存放 ClassComponent 这样的节点的
    • 我要删除这个dom节点的同时,相当于也要删除了这个 ClassComponent
    • 这个 ClassComponent 如果有生命周期方法,比如说 componentWillUnmount 这种方法
    • 那么我们要去提醒它,要去调用这个方法, 如何去知道有没有呢?我们就需要去遍历子树中的每一个节点
    • 同样的还有对于像 portal 这种方法,我们要从它的 container 里面去把它相关的 dom 节点去删除
    • 这也是我们要遍历子树的一个原因,所以这个过程是无法避免的
    • 遍历子树需要递归的过程
  • 卸载 ref
    • 因为我们这个doomm上面如果挂载了ref这个属性
    • 那么我们在render这个dom节点它的 owner 上面
    • 比如说 ClassComponent上面, 某个 ref 属性是指向这个dom节点
    • 如果已经把这个dom节点删掉了, 这个ref如果还指向这个dom节点,肯定是不对的
    • 这个时候, 要卸载这个ref
  • 若有组件,需调用它的 componentWillUnmount 的生命周期方法

2 )源码

定位到 packages/react-reconciler/src/ReactFiberCommitWork.js#L1021

查看 commitDeletion

function commitDeletion(current: Fiber): void {
  // dom 环境 默认 true
  if (supportsMutation) {
    // Recursively delete all host nodes from the parent.
    // Detach refs and call componentWillUnmount() on the whole subtree.
    unmountHostComponents(current);
  } else {
    // Detach refs and call componentWillUnmount() on the whole subtree.
    commitNestedUnmounts(current);
  }
  detachFiber(current);
}
  • 进入 unmountHostComponents

    function unmountHostComponents(current): void {
      // We only have the top Fiber that was deleted but we need recurse down its
      // children to find all the terminal nodes.
      let node: Fiber = current;
    
      // Each iteration, currentParent is populated with node's host parent if not
      // currentParentIsValid.
      let currentParentIsValid = false;
    
      // Note: these two variables *must* always be updated together.
      let currentParent;
      let currentParentIsContainer;
    
      while (true) {
        if (!currentParentIsValid) {
          let parent = node.return;
          // 进入while循环
          findParent: while (true) {
            invariant(
              parent !== null,
              'Expected to find a host parent. This error is likely caused by ' +
                'a bug in React. Please file an issue.',
            );
            // 如果它们是这 3 个之一,它们就会跳出这个 while 循环
            switch (parent.tag) {
              case HostComponent:
                currentParent = parent.stateNode;
                currentParentIsContainer = false;
                break findParent; // 注意,break的是上面对应的while循环,而非当前 switch, 下同如此
              case HostRoot:
                currentParent = parent.stateNode.containerInfo;
                currentParentIsContainer = true;
                break findParent;
              case HostPortal:
                currentParent = parent.stateNode.containerInfo;
                currentParentIsContainer = true;
                break findParent;
            }
            // 没有符合条件的,向上去找
            parent = parent.return;
          }
          // 跳出这个while循环之后,他就会设置 parentparentisvalid 为 true
          currentParentIsValid = true;
        }
    
        if (node.tag === HostComponent || node.tag === HostText) {
          commitNestedUnmounts(node);
          // After all the children have unmounted, it is now safe to remove the
          // node from the tree.
          // 上面操作的 currentParentIsContainer 变量,执行不同的 remove 方法,确定从哪里删掉
          if (currentParentIsContainer) {
            removeChildFromContainer((currentParent: any), node.stateNode); // 从container中删除
          } else {
            removeChild((currentParent: any), node.stateNode); // 从父节点中删除
          }
          // Don't visit children because we already visited them.
        } else if (node.tag === HostPortal) {
          // When we go into a portal, it becomes the parent to remove from.
          // We will reassign it back when we pop the portal on the way up.
          currentParent = node.stateNode.containerInfo;
          currentParentIsContainer = true;
          // Visit children because portals might contain host components.
          if (node.child !== null) {
            node.child.return = node;
            node = node.child;
            continue; // 找到 child
          }
        } else {
          commitUnmount(node);
          // Visit children because we may find more host components below.
          if (node.child !== null) {
            node.child.return = node;
            node = node.child;
            continue;
          }
        }
        // 整棵树遍历完了,回到了顶点,结束
        if (node === current) {
          return;
        }
        // 树没有兄弟节点,向上去寻找
        // 进入了这个循环,说明一侧的子树找完了,开始找兄弟节点了
        while (node.sibling === null) {
          if (node.return === null || node.return === current) {
            return;
          }
          node = node.return; // 向上寻找
          if (node.tag === HostPortal) {
            // When we go out of the portal, we need to restore the parent.
            // Since we don't keep a stack of them, we will search for it.
            currentParentIsValid = false;
          }
        }
        // 在循环的最外面,找兄弟节点
        node.sibling.return = node.return;
        node = node.sibling; // 找兄弟节点
      }
    }
    
    • 进入 commitUnmount
      // User-originating errors (lifecycles and refs) should not interrupt
      // deletion, so don't let them throw. Host-originating errors should
      // interrupt deletion, so it's okay
      function commitUnmount(current: Fiber): void {
        onCommitUnmount(current);
      
        switch (current.tag) {
          case FunctionComponent:
          case ForwardRef:
          case MemoComponent:
          case SimpleMemoComponent: {
            const updateQueue: FunctionComponentUpdateQueue | null = (current.updateQueue: any);
            if (updateQueue !== null) {
              const lastEffect = updateQueue.lastEffect;
              if (lastEffect !== null) {
                const firstEffect = lastEffect.next;
                let effect = firstEffect;
                do {
                  const destroy = effect.destroy;
                  if (destroy !== null) {
                    safelyCallDestroy(current, destroy);
                  }
                  effect = effect.next;
                } while (effect !== firstEffect);
              }
            }
            break;
          }
          case ClassComponent: {
            // 这里是卸载 ref 的操作
            // 因为Ref是可以作用在classcomponent上面的
            // classcomponent,具有instance而不像function component 没有 instance
            safelyDetachRef(current);
            const instance = current.stateNode;
      
            // 然后需要调用它的 componentWillUnmount 这个方法
            if (typeof instance.componentWillUnmount === 'function') {
              safelyCallComponentWillUnmount(current, instance);
            }
            return;
          }
          case HostComponent: {
            safelyDetachRef(current); // 只卸载 ref
            return;
          }
          case HostPortal: {
            // TODO: this is recursive.
            // We are also not using this parent because
            // the portal will get pushed immediately.
            if (supportsMutation) {
              unmountHostComponents(current); // 注意,这里走了一个递归,也就是调用上级函数,对应上面的 commitNestedUnmounts
            } else if (supportsPersistence) {
              emptyPortalContainer(current);
            }
            return;
          }
        }
      }
      
    • 进入 commitNestedUnmounts
      // 要调用这个方法,说明我们遇到了一个 HostComponent 节点或 HostText 节点,主要是针对 HostComponent
      function commitNestedUnmounts(root: Fiber): void {
        // While we're inside a removed host node we don't want to call
        // removeChild on the inner nodes because they're removed by the top
        // call anyway. We also want to call componentWillUnmount on all
        // composites before this host node is removed from the tree. Therefore
        // we do an inner loop while we're still inside the host node.
        let node: Fiber = root;
        // 一进来就是一个 while true 循环,对每一个节点执行 commitUnmount
        // 在这个过程中如果找到了有 HostPortal,也对它执行这个方法
        // 它又会去调用我们刚才的那个方法,这就是一个嵌套的递归调用的一个过程
        // 最终目的是要把整个子树给它遍历完成
        while (true) {
          commitUnmount(node); // 注意这里,一进来就执行这个,这个方法就是上面的那个方法
          // Visit children because they may contain more composite or host nodes.
          // Skip portals because commitUnmount() currently visits them recursively.
          if (
            node.child !== null &&
            // If we use mutation we drill down into portals using commitUnmount above.
            // If we don't use mutation we drill down into portals here instead.
            (!supportsMutation || node.tag !== HostPortal)
          ) {
            node.child.return = node;
            node = node.child;
            continue;
          }
          if (node === root) {
            return;
          }
          // node 一定是 root 节点的子树, 向上找含有兄弟节点的节点
          while (node.sibling === null) {
            if (node.return === null || node.return === root) {
              return;
            }
            node = node.return;
          }
          // 找它的 sibling 兄弟节点,继续执行 while 循环
          node.sibling.return = node.return;
          node = node.sibling;
        }
      }
      
      • 上面的代码完美阐述了删除中间的某个节点,如何处理其子节点的过程,包含 portal 的处理
  • commitDeletion 描述了整个删除的流程

  • 最重要的就是理解这个算法它如何进行递归的调用来遍历整棵子树每一个节点的过程

  • 对于 Portal,ClassComponent,还有 HostComponent,会有不同的操作

  • 需要注意的是,对于HostComponent的子树的遍历会放到这个 commitNestedUnmounts 方法里面去做

  • 对于这个 unmountHostComponents 方法,它遍历的过程的目的是

    • 找到所有的 HostComponent 来调用这个 commitNestedUnmounts 方法
    • 对于 Portal 和 ClassComponent,它们都会去找自己的 child 的节点
    • 而只有对于 HostCommonent,它才会调用嵌套的递归的方法来遍历它的子树
  • 对于这个整体流程,用下面的图来看下,比如说,要删除图中 App下的 div 节点

第一种场景

  • 对这个节点调用了 commitUnmount 方法
  • 然后去找它的child就是Input, 同样也调用 commitUnmount 这个方法
  • 它符合 if ( node.child !== null && (!supportsMutation || node.tag !== HostPortal) ) 这个条件,并 continue
  • 继续向下找,找到 input, 同样调用 commitUnmount 这个方法, 这时候,node.child === null, node !== root, 于是会执行
    while (node.sibling === null) {
      if (node.return === null || node.return === root) {
        return;
      }
      node = node.return;
    }
    // 找它的 sibling 兄弟节点,继续执行 while 循环
    node.sibling.return = node.return;
    node = node.sibling;
    
  • 这时候要找 input的兄弟节点,没有兄弟节点,符合 while (node.sibling === null)
  • 这时候执行这个 while, 向上查找到 Input, 发现 Input是有兄弟节点的,不符合 while (node.sibling === null),跳出
  • 这时候,node 就是 List (Input的兄弟节点),对 List 节点执行 commitUnmount 方法,继续执行
  • if ( node.child !== null && (!supportsMutation || node.tag !== HostPortal) ) 这里
  • List的child存在,并且List不是HostPortal, 这时候就向下去查找,就到了第一个 span 节点
  • 这时候,span节点没有child, 就会找它的sibling, 找到button,发现没有兄弟节点了,就找它的return
  • 最后一个button的return是 List, 而List又是当前循环的root, 这时候,整个方法,内外循环都停止了
  • 这个过程,我们把每个节点都遍历到了,对每个节点都执行了 commitUnmount 方法

第二种场景

  • 与第一种场景不同,这里的第一个span变成了 Portal,其下有一个 div
  • 前一部分与第一种场景类似,当遍历到 Portal 时,调用 commitUnmount 方法,进入其内部
  • 在 switch case 中匹配到了 HostPortal,调用了 unmountHostComponents 方法,并进入其内部
  • 在 else if 中匹配到了 HostPortal,存在child, 找到其child, 也就是 div 节点,继续内部循环
  • 匹配到了 HostComponent, 需要调用 commitNestedUnmounts, 这个div只有一个节点,执行完成后
  • 接着调用下面的 removeChildFromContainer 方法,因为对于 Portal来说,currentParentIsContainer 是 true
  • 接着往下执行到 while 里面的 if (node.return === null || node.return === root),它的 return 是root,由此返回结束循环
  • 返回到 调用的 commitUnmount 里面, 看到 case HostPortal 最后是return, 也就是这个方法结束了
  • 返回到 commitNestedUnmounts 的 while true 里面的 commitUnmount 下面的代码继续执行,会跳过2个if
  • 直接进入 while, 这时候会找 Portal 节点的sibling, 也就是 span, 接着重复场景1向上返回,最终返回到App之下的这个div

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/351667.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

行业应用 | Sophon AutoCV推动AI应用从模型生产到高效落地

随着技术市场和应用方向的逐渐成熟,人工智能与各行各业的结合和落地逐渐进入了深水区。 虽然由于行业规模化和应用普及度的限制,人工智能在“传统”行业的落地不如消费互联网行业,但是借助人工智能为“传统”行业的发展注入新能量一直是相关…

大文件传输之以太网UDP传输延迟解决方案

在数字化浪潮席卷全球的今天,数据已成为企业最宝贵的资产之一。随着企业规模的扩大和业务的全球化,大文件传输的需求日益增长,它不仅关系到企业内部数据的高效管理,也是与外部合作伙伴进行有效沟通的关键。然而,大文件…

STM32——创建HAL库工程

第一步 新建工程文件夹 所有文件夹存放的内容: 第二步 拷贝/新建工程相关文件 1、Drivers(创建如下文件夹,除readme.txt) 文件夹存放的内容: 2、Middlewares和output暂时不需要创建(目前用不到&#x…

幻兽帕鲁一键开私服?超简单小白教程一看就会!

如何自建幻兽帕鲁服务器?基于阿里云服务器搭建幻兽帕鲁palworld服务器教程来了,一看就懂系列。本文是利用OOS中幻兽帕鲁扩展程序来一键部署幻兽帕鲁服务器,阿里云服务器网aliyunfuwuqi.com分享官方基于阿里云服务器快速创建幻兽帕鲁服务器教程…

ARM常用汇编指令

文章目录 前言一、处理器内部数据传输指令MOV: 将数据从一个寄存器复制到另一个寄存器。MRS: 将特殊寄存器(CPSR,SPSR)中的数据传给通用寄存器。MSR: 将通用寄存器中的数据传给特殊寄存器(CPSR,SPSR)。 二、存储器访问指令LDR:用于从内存中加…

Shell脚本——函数的使用

一、函数 1.函数的作用 定义较为复杂的但是需要重复使用的内容,以便再次使用可以直接调用函数节约时间,提高效率 在编写脚本时,有些脚本可以反复使用,可以调用函数来解决,语句块定义成函数约等于别名 函数定义&…

51-17 视频理解串讲— MViT 论文精读

继TimeSformer模型之后,咱们再介绍两篇来自Facebook AI的论文,即Multiscale Vision Transformers以及改进版MViTv2: Improved Multiscale Vision Transformers for Classification and Detection。 由于本司大模型组最近组织阅读的论文较多,…

【iOS ARKit】同时开启前后摄像头BlendShapes

在上一节中已经了解了 iOS ARkit 进行BlendShapes的基本操作,这一小节继续实践同时开启前后摄像头进行人脸捕捉和世界追踪。 iOS设备配备了前后两个摄像头,在运行AR 应用时,需要选择使用哪个摄像头作为图像输人。最常见的AR 体验使用设备后置…

Java实现数字化社区网格管理系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、开发背景四、系统展示五、核心源码5.1 查询企事业单位5.2 查询流动人口5.3 查询精准扶贫5.4 查询案件5.5 查询人口 六、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的数字化社区网格管理系统&#xf…

Istio-gateway

一. gateway 在 Kubernetes 环境中,Kubernetes Ingress用于配置需要在集群外部公开的服务。但是在 Istio 服务网格中,更好的方法是使用新的配置模型,即 Istio Gateway,Gateway 允许将 Istio 流量管理的功能应用于进入集群的流量&…

MongoDB日期存储与查询、@Query、嵌套字段查询实战总结

缘由 MongoDB数据库如下: 如上截图,使用MongoDB客户端工具DataGrip,在filter过滤框输入{ profiles.alias: 逆天子, profiles.channel: },即可实现昵称和渠道多个嵌套字段过滤查询。 现有业务需求:用Java代码来查询…

【代码随想录15】110.平衡二叉树 257. 二叉树的所有路径 404.左叶子之和

目录 110. 平衡二叉树题目描述参考代码 257. 二叉树的所有路径题目描述参考代码 404.左叶子之和题目描述参考代码 110. 平衡二叉树 题目描述 给定一个二叉树,判断它是否是高度平衡的二叉树。 本题中,一棵高度平衡二叉树定义为: 一个二叉树…

微信小程序(十四)分包和分包预加载

注释很详细,直接上代码 上一篇 新增内容: 1.分包的配置 2.分包预加载的写法 先说说为什么需要分包: 小程序追求小而快,主包的大小控制是小程序上线的硬性要求,分包有利于小程序优化加载速度 分包的注意事项&#xff1a…

JVM篇:垃圾回收

如何判断对象可以被回收 Java中对象能否被回收,是根据兑现是否被引用来决定的。如果对象被引用了,说明该对象还在使用,不允许被回收 main栈帧中demo变量存储着Demo实例对象的地址,与Demo实例对象建立了连接关系此时Demo实例对象可…

2024新版68套Axure RP大数据可视化大屏模板及通用组件+PSD源文件

Axure RP数据可视化大屏模板及通用组件库2024新版重新制作了这套新的数据可视化大屏模板及通用组件库V2版。新版本相比于V1版内容更加丰富和全面,但依然秉承“敏捷易用”的制作理念,这套作品也同样延续着我们对细节的完美追求,整个设计制作过…

关于binlog文件恢复数据库的方法

今天给大家讲解下,binlog日志恢复数据库的方法,之前由于数据库中了勒索病毒,这期文章告诉你恢复的方法:下面这种千万不要支付,支付了也不会给恢复 找到binlog文件: 这里我只恢复00032和00033即可&#xff1…

鸿蒙开发初体验

文章目录 前言一、环境配置1.1 安装DevEco Studio1.2 安装相关环境 二、工程创建三、工程结构介绍四、代码实现4.1 初识ArkTs4.2 具体实现 参考资料 前言 HarmonyOS是华为公司推出的一种操作系统,旨在为不同设备提供统一的操作系统和开发平台。鸿蒙开发的出现为用户…

【深度学习】sdxl中的 text_encoder text_encoder_2 区别

镜像问题是:https://editor.csdn.net/md/?articleId135867689 代码仓库: https://huggingface.co/stabilityai/stable-diffusion-xl-base-1.0/tree/main 截图: 为什么有两个CLIP编码器 text_encoder 和 text_encoder_2 ? 在…

照片怎么弄成jpg格式文件?jpg图片格式转换器介绍

jpg图片格式作为最常用的图片格式类型之一,不管是平时下载还是拍摄的照片大多数都属于jpg格式,还有我们在制作证件照照片时,通常需要将照片转换成jpg格式,以便更好地保存、打印或上传至网站等,那么图片转换为jpg需要怎…

day31_HTML

今日内容 0 复习昨日 1 表格标签 2 表单标签【重要】 3 框架标签 0 复习昨日 Javaweb开发,前端,服务器,数据库 前端,要学习HTML,CSS,JavaScript,JQuery HTML是用来编写网页的一种编程语言 语法 由各种标签组成,标签是尖括号<>,一般都是成对儿出现,前面叫做开标签,后面…
最新文章