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,它才会调用嵌套的递归的方法来遍历它的子树
- 找到所有的 HostComponent 来调用这个
-
对于这个整体流程,用下面的图来看下,比如说,要删除图中 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࿰…...

[机器学习]简单线性回归——梯度下降法
一.梯度下降法概念 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学习计划中的最后一天。 在数据处理和数据分析中,数据预处理是非常重要的一步。我们不可能完全靠肉眼来分析数据,总会有用到各种算法模型的时候…...
SciencePlots——绘制论文中的图片
文章目录 安装一、风格二、1 资源 安装 # 安装最新版 pip install githttps://github.com/garrettj403/SciencePlots.git# 安装稳定版 pip install SciencePlots一、风格 简单好用的深度学习论文绘图专用工具包–Science Plot 二、 1 资源 论文绘图神器来了:一行…...

基于当前项目通过npm包形式暴露公共组件
1.package.sjon文件配置 其中xh-flowable就是暴露出去的npm包名 2.创建tpyes文件夹,并新增内容 3.创建package文件夹...
土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等
🔍 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术,可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势,还能有效评价重大生态工程…...

【Java_EE】Spring MVC
目录 Spring Web MVC 编辑注解 RestController RequestMapping RequestParam RequestParam RequestBody PathVariable RequestPart 参数传递 注意事项 编辑参数重命名 RequestParam 编辑编辑传递集合 RequestParam 传递JSON数据 编辑RequestBody …...

如何在网页里填写 PDF 表格?
有时候,你可能希望用户能在你的网站上填写 PDF 表单。然而,这件事并不简单,因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件,但原生并不支持编辑或填写它们。更糟的是,如果你想收集表单数据ÿ…...

算法笔记2
1.字符串拼接最好用StringBuilder,不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...
Go 语言并发编程基础:无缓冲与有缓冲通道
在上一章节中,我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道,它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好࿰…...
【LeetCode】3309. 连接二进制表示可形成的最大数值(递归|回溯|位运算)
LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 题目描述解题思路Java代码 题目描述 题目链接:LeetCode 3309. 连接二进制表示可形成的最大数值(中等) 给你一个长度为 3 的整数数组 nums。 现以某种顺序 连接…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
API网关Kong的鉴权与限流:高并发场景下的核心实践
🔥「炎码工坊」技术弹药已装填! 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中,API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关,Kong凭借其插件化架构…...