当前位置: 首页 > news >正文

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 环境 默认 trueif (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 为 truecurrentParentIsValid = 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 没有 instancesafelyDetachRef(current);const instance = current.stateNode;// 然后需要调用它的 componentWillUnmount 这个方法if (typeof instance.componentWillUnmount === 'function') {safelyCallComponentWillUnmount(current, instance);}return;}case HostComponent: {safelyDetachRef(current); // 只卸载 refreturn;}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

相关文章:

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

commitDeletion 1 )概述 在 react commit 阶段的 commitRoot 第二个while循环中调用了 commitAllHostEffects,这个函数不仅仅处理了新增节点,更新节点最后一个操作,就是删除节点,就需要调用 commitDeletion&#xff0…...

[机器学习]简单线性回归——梯度下降法

一.梯度下降法概念 2.代码实现 # 0. 引入依赖 import numpy as np import matplotlib.pyplot as plt# 1. 导入数据(data.csv) points np.genfromtxt(data.csv, delimiter,) points[0,0]# 提取points中的两列数据,分别作为x,y …...

2024年搭建幻兽帕鲁服务器价格多少?如何自建Palworld?

自建幻兽帕鲁服务器租用价格表,2024阿里云推出专属幻兽帕鲁Palworld游戏优惠服务器,配置分为4核16G和4核32G服务器,4核16G配置32.25元/1个月、3M带宽96.75元/1个月、8核32G配置10M带宽90.60元/1个月,8核32G配置3个月271.80元。ECS…...

『OpenCV-Python|鼠标作画笔』

Opencv-Python教程链接:https://opencv-python-tutorials.readthedocs.io/ 本文主要介绍OpenCV-Python如何将鼠标作画笔绘制圆或者矩形。 示例一:图片上双击的位置绘制一个圆圈 首先创建一个鼠标事件回调函数,鼠标事件发生时就会被执行。鼠标…...

关于如何利用ChatGPT提高编程效率的

自从去年ChatGPT3.5推出以后,这一年时间在编程过程中我也在慢慢熟悉人工智能的使用,目前来看即使是免费的ChatGPT3.5对于编程效率的提升也是有很大帮助的,虽然在使用过程中确实出现了一些问题,本文记录下我的一些心得体会和用法。…...

Excel VBA ——从MySQL数据库中导出一个报表-笔记

本文主要涉及: VBA中数据库连接参数改成从配置文件获取 VBA连接MySQL数据库 VBA读MySQL数据库 演示两种写入工作簿的代码实现系统环境: Windows 10 64bit Excel 365 64bit WAMP(3.2.2.2 64bit)集成的MariaDB版本为10.4.10&#…...

金融OCR领域实习日志(一)——OCR技术从0到1全面调研

一、OCR基础 任务要求: 工作原理 OCR(Optical Character Recognition,光学字符识别)是指电子设备(例如扫描仪或数码相)检查纸上打印的字符,经过检测暗、亮的模式肯定其形状,而后用…...

ELK日志解决方案

ELK日志解决方案 ELK套件日志系统应该是Elasticsearch使用最广泛的场景之一了,Elasticsearch支持海量数据的存储和查询,特别适合日志搜索场景。广泛使用的ELK套件(Elasticsearch、Logstash、Kibana)是日志系统最经典的案例,使用Logstash和Be…...

嵌入式学习-驱动

嵌入式的一些基本概念 CPU与MCU的区别 CPU(中央处理器,central processing unit) 指集成了运算器、控制器、寄存器、高速缓存等功能模块的芯片,负责执行计算机程序指令的处理器。MCU(单片微型计算机或单片机,microco…...

系统架构17 - 软件工程(5)

软件工程 软件测试测试原则测试方法静态测试动态测试黑盒测试白盒测试灰盒测试自动化测试 测试阶段单元测试集成测试系统测试性能测试验收测试其它测试AB测试Web测试链接测试表单测试 测试用例设计黑盒测试用例白盒测试用例 调试 系统维护遗留系统系统转换转换方式数据转换与迁…...

空气质量预测 | Python实现基于线性回归、Lasso回归、岭回归、决策树回归的空气质量预测模型

文章目录 效果一览文章概述源码设计参考资料效果一览 文章概述 政府机构使用空气质量指数 (AQI) 向公众传达当前空气污染程度或预测空气污染程度。 随着 AQI 的上升,公共卫生风险也会增加。 不同国家有自己的空气质量指数,对应不同国家的空气质量标准。 对于空气质量预测,…...

MYSQL数据库基本操作-DQL-基本查询

一.概念 数据库管理系统一个重要功能就是数据查询。数据查询不应是简单返回数据库中存储的数据,还应该根据需要对数据进行筛选以及确定数据以什么样的格式显示。 MySQL提供了功能强大,灵活的语句来实现这些操作。 MySQL数据库使用select语句来查询数据…...

gdb 调试 - 在vscode图形化展示在远程的gdb debug过程

前言 本地机器的操作系统是windows,远程机器的操作系统是linux,开发在远程机器完成,本地只能通过ssh登录到远程。现在目的是要在本地进行图形化展示在远程的gdb debug过程。(注意这并不是gdb remote !!&am…...

Android 13.0 SystemUI下拉状态栏定制二 锁屏页面横竖屏时钟都居中功能实现二

1.前言 在13.0的系统rom定制化开发中,在关于systemui的锁屏页面功能定制中,由于在平板横屏锁屏功能中,时钟显示的很大,并且是在左旁边居中显示的, 由于需要和竖屏显示一样,所以就需要用到小时钟显示,然后同样需要居中,所以就来分析下相关的源码,来实现具体的功能 如图…...

docker 部署xxl-job

docker 部署xxl-job XXL-JOB github地址 https://github.com/xuxueli/xxl-job XXL-JOB 文档地址 https://www.xuxueli.com/xxl-job/ XXL-JOB是一个分布式任务调度平台,其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品…...

Kafka(九)跨集群数据镜像

目录 1 跨集群镜像的应用场景1.1 区域集群和中心集群1.2 高可用(HA)和灾备(DR)1.3 监管与合规1.4 云迁移1.5 聚合边缘集群的数据 2 多集群架构2.1 星型架构2.2 双活架构2.2 主备架构2.2.1 如何实现Kafka集群的故障转移2.2.1.1 故障转移包括的内容1. 灾难恢复计划2. 非计划内的故…...

第3讲 谈谈final、finally、 finalize有什么不同?

参考 三者区别 final final 是 java 关键字可修饰变量(类成员变量、类静态变量、局部变量和形参):表示不可修改当前变量的值(这里的值可以是地址,也可以是基本类型的值)#(注意:fi…...

MC3172 串口模块

MC3172 支持12个串口对应关系如下 串口模块初始化 第一个是uart0~11 inpin RX 脚 管脚号 outpin TX脚 管脚号 baud 波特率 read_ptr ,数据读取指针 void uart_init(u32 uart_num,u8 in_pin,u8 out_pin,u32 baud,u8* read_ptr) {INTDEV_SET_CLK_RST(uart_num,(INTDEV_RUN|…...

VUE3 加载自定义SVG文件

网上代码通篇一律,需要修改多处地方,特别是component下还要创建一个index.vue的组件,奇奇怪怪。 要在 Vue 项目中使用 svg-sprite-loader 来管理 SVG 图标,你需要执行以下几个步骤: npm install svg-sprite-loader -…...

【数据分析】numpy基础第五天

文章目录 前言Z-Score标准化Z-Score应用示例 Min-Max归一化Min-Max应用示例 总结 前言 第五天是我们的numpy学习计划中的最后一天。 在数据处理和数据分析中,数据预处理是非常重要的一步。我们不可能完全靠肉眼来分析数据,总会有用到各种算法模型的时候…...

CSS 双色拼接按钮效果

<template><view class="sss"><button> <!-- 按钮 --><view class="span"> 按钮 </view> <!-- 按钮文本 --></button></view></template><script></script><style>body {b…...

T05垃圾收集算法与垃圾收集器ParNew CMS

垃圾收集算法与垃圾收集器ParNew & CMS 垃圾收集算法 #### f 分代收集理论 当前虚拟机的垃圾收集都采用分代收集算法。根据对象存活周期不同将内存分为几块&#xff0c;一般将java堆分为新生代和老年代&#xff0c;然后根据各个年代的特点选择不同的垃圾收集算法。 在新…...

每日一道面试题:Java中序列化与反序列化

写在开头 哈喽大家好&#xff0c;在高铁上码字的感觉是真不爽啊&#xff0c;小桌板又拥挤&#xff0c;旁边的小朋友也比较的吵闹&#xff0c;影响思绪&#xff0c;但这丝毫不影响咱学习的劲头&#xff01;哈哈哈&#xff0c;在这喧哗的车厢中&#xff0c;思考着这样的一个问题…...

论文阅读:Vary-toy论文阅读笔记

目录 引言整体结构图方法介绍训练vision vocabulary阶段PDF数据目标检测数据 训练Vary-toy阶段Vary-toy结构数据集情况 引言 论文&#xff1a;Small Language Model Meets with Reinforced Vision Vocabulary Paper | Github | Demo 说来也巧&#xff0c;之前在写论文阅读&…...

【Linux】开始使用 vim 吧!!!

Linux 1 what is vim &#xff1f;2 vim基本概念3 vim的基本操作 &#xff01;3.1 vim的快捷方式3.1.1 复制与粘贴3.1.2 撤销与剪切3.1.3 字符操作 3.2 vim的光标操作3.3 vim的文件操作 总结Thanks♪(&#xff65;ω&#xff65;)&#xff89;感谢阅读下一篇文章见&#xff01;…...

多线程面试合集

前言 前文介绍了JVM相关知识&#xff0c;本文将重点介绍多线程相关知识以及工作中的一些经验。 多线程面试合集 什么是多线程&#xff1f;为什么我们需要多线程&#xff1f; 多线程是指在一个进程中同时执行多个线程&#xff0c;每个线程可以执行不同的任务。多线程可以提高…...

从微服务到云原生

很多文章介绍云原生概念&#xff0c;说它包含微服务&#xff0c;又包含了其它几个方面的东西&#xff0c;还扯到文化层面、组织层面和技术层面&#xff0c;搞技术的人一听到公司文化问题和组织部门问题&#xff0c;就十分地晕眩&#xff0c;不能让我好好地坐下来写写代码、搞搞…...

bxCAN 主要特性

bxCAN 主要特性 ● 支持 2.0 A 及 2.0 B Active 版本 CAN 协议 ● 比特率高达 1 Mb/s ● 支持时间触发通信方案 发送 ● 三个发送邮箱 ● 可配置的发送优先级 ● SOF 发送时间戳 接收 ● 两个具有三级深度的接收 FIFO ● 可调整的筛选器组&#xff1a; — CAN1 和…...

武忠祥2025高等数学,基础阶段的百度网盘+视频及PDF

考研数学武忠祥基础主要学习以下几个方面的内容&#xff1a; 1.微积分:主要包括极限、连续、导数、积分等概念&#xff0c;以及它们的基本性质和运算方法。 2.线性代数:主要包括向量、向量空间、线性方程组、矩阵、行列式、特征值和特征向量等概念&#xff0c;以及它们的基本…...

用JavaFX写了一个简易的管理系统

文章目录 前言正文一、最终效果1.1 主页面1.2 动物管理页面-初始化1.3 动物管理页面-修改&新增1.4 动物管理页面-删除&批量删除 二、核心代码展示2.1 启动类2.2 数据库配置-db.setting2.3 日志文本域组件2.4 自定义表格视图组件2.5 自定义分页组件2.6 动物管理页面2.7 …...