React源码分析8-状态更新的优先级机制

news2024/11/18 3:42:21

这是我的剖析 React 源码的第二篇文章,如果你没有阅读过之前的文章,请务必先阅读一下 第一篇文章 中提到的一些注意事项,能帮助你更好地阅读源码。

文章相关资料

  • React 16.8.6 源码中文注释,这个链接是文章的核心,文中的具体代码及代码行数都是依托于这个仓库
  • 热身篇
  • render 流程(二)

现在请大家打开 我的代码 并定位到 react-dom 文件夹下的 src 中的 ReactDOM.js 文件,今天的内容会从这里开始。

render

想必大家在写 React 项目的时候都写过类似的代码

ReactDOM.render(<APP />, document.getElementById('root')

这句代码告诉了 React 应用我们想在容器中渲染出一个组件,这通常也是一个 React 应用的入口代码,接下来我们就来梳理整个 render 的流程,并且会分为几篇文章来讲解,因为流程实在太长了。

首先请大家先定位到 ReactDOM.js 文件的第 702 行代码,开始今天的旅程。

这部分代码其实没啥好说的,唯一需要注意的是在调用 legacyRenderSubtreeIntoContainer 函数时写死了第四个参数 forceHydratefalse。这个参数为 true 时表明了是服务端渲染,因为我们分析的是客户端渲染,因此后面有关这部分的内容也不会再展开。

接下来进入 legacyRenderSubtreeIntoContainer 函数中,这部分代码分为两块来讲。第一部分是没有 root 之前我们首先需要创建一个 root(对应这篇文章),第二部分是有 root 之后的渲染流程(对应接下来的文章)。

一开始进来函数的时候肯定是没有 root 的,因此我们需要去创建一个 root,大家可以发现这个 root 对象同样也被挂载在了 container._reactRootContainer 上,也就是我们的 DOM 容器上。
如果你手边有 React 项目的话,在控制台键入如下代码就可以看到这个 root 对象了。

document.querySelector('#root')._reactRootContainer

大家可以看到 rootReactRoot 构造函数构造出来的,并且内部有一个 _internalRoot 对象,这个对象是本文接下来要重点介绍的 fiber 对象,接下来我们就来一窥究竟吧。

首先还是和上文中提到的 forceHydrate 属性相关的内容,不需要管这部分,反正 shouldHydrate 肯定为 false

接下来是将容器内部的节点全部移除,一般来说我们都是这样写一个容器的的

<div id='root'></div>

这样的形式肯定就不需要去移除子节点了,这也侧面说明了一点那就是容器内部不要含有任何的子节点。一是肯定会被移除掉,二来还要进行 DOM 操作,可能还会涉及到重绘回流等等。

最后就是创建了一个 ReactRoot 对象并返回。接下来的内容中我们会看到好几个 root,可能会有点绕。

ReactRoot 构造函数内部就进行了一步操作,那就是创建了一个 FiberRoot 对象,并挂载到了 _internalRoot 上。和 DOM 树一样,fiber 也会构建出一个树结构(每个 DOM 节点一定对应着一个 fiber 对象),FiberRoot 就是整个 fiber 树的根节点,接下来的内容里我们将学习到关于 fiber 相关的内容。这里提及一点,fiber 和 Fiber 是两个不一样的东西,前者代表着数据结构,后者代表着新的架构。

createFiberRoot 函数内部,分别创建了两个 root,一个 root 叫做 FiberRoot,另一个 root 叫做 RootFiber,并且它们两者还是相互引用的。

这两个对象内部拥有着数十个属性,现在我们没有必要一一去了解它们各自有什么用处,在当下只需要了解少部分属性即可,其他的属性我们会在以后的文章中了解到它们的用处。

对于 FiberRoot 对象来说,我们现在只需要了解两个属性,分别是 containerInfocurrent。前者代表着容器信息,也就是我们的 document.querySelector('#root');后者指向 RootFiber

对于 RootFiber 对象来说,我们需要了解的属性稍微多点

function FiberNode(
  tag: WorkTag,
  pendingProps: mixed,
  key: null | string,
  mode: TypeOfMode,
) {
  this.stateNode = null;
  this.return = null;
  this.child = null;
  this.sibling = null;
  this.effectTag = NoEffect;
  this.alternate = null;
}

stateNode 上文中已经讲过了,这里就不再赘述。

returnchildsibling 这三个属性很重要,它们是构成 fiber 树的主体数据结构。fiber 树其实是一个单链表树结构,returnchild 分别对应着树的父子节点,并且父节点只有一个 child 指向它的第一个子节点,即便是父节点有好多个子节点。那么多个子节点如何连接起来呢?答案是 sibling,每个子节点都有一个 sibling 属性指向着下一个子节点,都有一个 return 属性指向着父节点。这么说可能有点绕,我们通过图来了解一下这个 fiber 树的结构。

const APP = () => (
    <div>
        <span></span>
        <span></span>
    </div>
)
ReactDom.render(<APP/>, document.querySelector('#root'))

假如说我们需要渲染出以上组件,那么它们对应的 fiber 树应该长这样

相关参考视频讲解:进入学习

从图中我们可以看到,每个组件或者 DOM 节点都会对应着一个 fiber 对象。另外你手边有 React 项目的话,也可以在控制台输入如下代码,查看 fiber 树的整个结构。

// 对应着 FiberRoot
const fiber = document.querySelector('#root')._reactRootContainer._internalRoot

另外两个属性在本文中虽然用不上,但是看源码的时候笔者觉得很有意思,就打算拿出来说一下。

在说 effectTag 之前,我们先来了解下啥是 effect,简单来说就是 DOM 的一些操作,比如增删改,那么 effectTag 就是来记录所有的 effect 的,但是这个记录是通过位运算来实现的,这里 是 effectTag 相关的二进制内容。

如果我们想新增一个 effect 的话,可以这样写 effectTag |= Update;如果我们想删除一个 effect 的话,可以这样写 effectTag &= ~Update

最后是 alternate 属性。其实在一个 React 应用中,通常来说都有两个 fiebr 树,一个叫做 old tree,另一个叫做 workInProgress tree。前者对应着已经渲染好的 DOM 树,后者是正在执行更新中的 fiber tree,还能便于中断后恢复。两棵树的节点互相引用,便于共享一些内部的属性,减少内存的开销。毕竟前文说过每个组件或 DOM 都会对应着一个 fiber 对象,应用很大的话组成的 fiber 树也会很大,如果两棵树都是各自把一些相同的属性创建一遍的话,会损失不少的内存空间及性能。

当更新结束以后,workInProgress tree 会将 old tree 替换掉,这种做法称之为 double buffering,这也是性能优化里的一种做法,有兴趣的同学可以自行查找资料。

总结

以上就是本文的全部内容了,最后通过一张流程图总结一下这篇文章的内容。

最后

阅读源码是一个很枯燥的过程,但是收益也是巨大的。如果你在阅读的过程中有任何的问题,都欢迎你在评论区与我交流。

另外写这系列是个很耗时的工程,需要维护代码注释,还得把文章写得尽量让读者看懂,最后还得配上画图,如果你觉得文章看着还行,就请不要吝啬你的点赞。

下一篇文章还是 render 流程相关的内容。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Maven打包同时引入本地jar包

Maven打包同时引入本地jar包 若依分离版作为案例 &#xff0c;实际测试 方法一(pom文件指定jar包目录进行引入) 1.将需要手动引入的jar包放入ruoyi-admin的resources下&#xff0c;例如&#xff1a; 2.手动jar依赖则在ruoyi-common模块下的pom.xml中引入 <dependency>…

Sqlite数据库操作(一)—— 命令行操作

目录 1、sqlite 数据库安装 2、数据库常用命令 (1) 创建数据库 (2) 系统命令 (3) sql语句 1、sqlite 数据库安装 在终端输入 sudo apt-get install sqlite3 安装sqlite3&#xff0c;安装完毕以后&#xff0c;在终端输入 sqlite3 &#xff0c;若出现以下内容&#xff0c;…

MySQL数据库学习(5)

一、概念 视图是数据库中常用对象之一&#xff0c;它的内容是数据库部分数据或以聚合等方式重构的数据。 只存放视图的定义&#xff0c;不存放数据。不存储数据&#xff0c;所以视图是一个虚表。 因为数据存在基本表中&#xff0c;基本表的数据发生变化&#xff0c;视图查询的结…

MySQL集群解决方案(1):MySQL数据库的集群方案

1、系统架构存在的问题 在我们的系统架构中&#xff0c;DBserver方面我们只是使用了单节点服务&#xff0c;如果面对大并发&#xff0c;海量数据的存储&#xff0c;显然单节点的系统架构将存在很严重的问题&#xff0c;所以接下来&#xff0c;我们将实现MySQL的集群&#xff0c…

双12有哪些宝藏数码好物值得入手、这份超值数码清单收藏好

作为一年一度的电商大促狂欢日&#xff0c;不知道各位小伙伴儿有没有入手哪些心仪的数码产品呢&#xff1f;如果确实不知道要入啥好&#xff0c;不妨一起来看看我为各位精心准备的这份最值得入手的数码产品清单吧&#xff0c;这份清单的产品最主要突出的是颜值、产品实力还有性…

anaconda使用arcpy库

anaconda使用Arcpy环境1. 查看Arcgis版本2. 创建虚拟环境3. 将anaconda环境放入jupyter中1. 查看Arcgis版本 找到Arcgis安装python路径【电脑需要下载Arcgis】点击python.exe&#xff0c;查看python版本 2. 创建虚拟环境 管理员身份打开Anaconda PowerShell Prompt 查看ana…

安卓玩机搞机技巧综合资源-----“另类更新“偷渡”操作步骤 无需解锁bl 无需内侧用户【十三】

接上篇 安卓玩机搞机技巧综合资源------如何提取手机分区 小米机型代码分享等等 【一】 安卓玩机搞机技巧综合资源------开机英文提示解决dm-verity corruption your device is corrupt. 设备内部报错 AB分区等等【二】 安卓玩机搞机技巧综合资源------EROFS分区格式 小米红…

如何排查CPU 100%的应用

一台机器&#xff0c;CPU100%&#xff0c;如何找到相关服务&#xff0c;如何定位问题代码&#xff0c;今天简单分享下思路。 简要步骤如下&#xff1a; 找到最耗CPU的进程&#xff1b;找到最耗CPU的线程&#xff1b;查看堆栈&#xff0c;定位线程在干嘛&#xff0c;定位对应代码…

linux安装docker教程+mysql安装

一、linux安装docker教程 第一步、关闭SELINUX服务 修改文件后的重启linux&#xff0c;重启命令为&#xff1a;reboot 注意此处虚拟机里修改重启无法启动linux系统成功&#xff0c;处理方法&#xff1a; 1、重启Linux按e进入系统启动项修改参数 2、在linux16的行位添加 seli…

k8s编程operator——(4) kubebuilder controller-runtime

文章目录1、KubeBuilder使用1.1 下载1.2 使用2、controller-runtime参考资料&#xff1a;https://book.kubebuilder.io/k8s编程operator系列&#xff1a; k8s编程operator——(1) client-go基础部分 k8s编程operator——(2) client-go中的informer k8s编程operator——(3) 自定…

【PostgreSQL的wal_buffers】

1、什么是wal buffer? wal buffer是预写日志(wal)缓冲区 2、wal buffer的作用是什么 用于还未写入磁盘的 WAL 数据的共享内存。 每次变更事务提交时候,需要将变更事务日志落盘&#xff0c;在PG中为了提高性能&#xff0c;并非采用实时flush到磁盘,而是在PG中提供XLog Buff…

DFS学习

一、DFS 1、简介 最初听到DFS还以为是深度优先遍历&#xff0c;实际上是分布式文件存储服务(Distributed file system)。 FastDFS是一套分布式文件存储服务&#xff0c;Titans将FastDFS提供的接口进行了封装&#xff0c;屏蔽了一些无需关注的操作细节&#xff0c;让用户可以更…

Java基础:Lambda表达式

1. 函数式编程思想概述 在数学中&#xff0c;函数就是有输入量、输出量的一套计算方案&#xff0c;也就是“拿什么东西做什么事情”。相对而言&#xff0c;面向对象过分强调“必须通过对象的形式来做事情”&#xff0c;而函数式思想则尽量忽略面向对象的复杂语法——强调做什么…

《Fluent Python》笔记 | 函数对象和装饰器

在Python中函数是对象&#xff0c;本质是function类的实例。同样函数对象也是“一等对象”&#xff0c;即满足以下条件&#xff1a; 在运行时创建能赋值给变量或数据结构中的元素能作为参数传给函数能作为函数的返回结果 函数对象的__doc__属性用于生成对象的帮助文本。 接受…

【避坑指南】快准狠!一键采购电子元器件(文末优惠券)

在采购元器件的过程中&#xff0c;经常会出现一些或这或那的情况&#xff0c;比如遇到假货问题、不具备专业知识、工作经验不够丰富、采购型号错误等等&#xff0c;因此采购下单如赌注&#xff0c;每个订单都下得心惊肉跳。 那么有哪些坑是可以避开的&#xff0c;又有什么方法可…

AI大模型加持,生成式搜索来了!

梦晨 发自 凹非寺量子位 | 公众号 QbitAI最近有两件事&#xff0c;让搜索引擎重回聚光灯下。百度发布“文心百中”&#xff0c;用AI大模型技术驱动的产业级搜索系统。构建企业内部搜索引擎的人力成本减少90%以上&#xff0c;同时只需要极低数据。几乎同一时间&#xff0c;OpenA…

算法---DFS和BFS

一 : 什么是DFS和BFS? 转载自 : 什么是DFS和BFS? 简介&#xff1a; 深度优先遍历&#xff08;Depth First Search, 简称 DFS&#xff09; 与广度优先遍历&#xff08;Breath First Search&#xff09;是图论中两种非常重要的算法&#xff0c;生产上广泛用于拓扑排序&#xf…

软考高级信息系统项目管理师如何备考?

从以下两个方面&#xff1a; 1.首先分析一下高项考试的各个科目&#xff1b; 2.如何备考高项&#xff1f; 高项考试有三个科目&#xff1a; 综合知识&#xff0c;案例分析&#xff0c;和论文。 一、综合知识 信息系统项目管理师上午综合知识科目范围广&#xff0c;知识点非…

【差分进化算法】基于适应度-距离-平衡的自适应引导差分进化 (FDB-AGDE) 算法附matlab代码

​✅作者简介&#xff1a;热爱科研的Matlab仿真开发者&#xff0c;修心和技术同步精进&#xff0c;matlab项目合作可私信。 &#x1f34e;个人主页&#xff1a;Matlab科研工作室 &#x1f34a;个人信条&#xff1a;格物致知。 更多Matlab仿真内容点击&#x1f447; 智能优化算法…

java工作流开源框架可以提高工作效率吗?

要想回答这个问题&#xff0c;就需要了解什么是java工作流开源框架&#xff0c;以及java工作流开源框架的主要特点是什么。随着大数据时代的拓展发展&#xff0c;低代码开发平台已经在数字化管理时代中深受欢迎&#xff0c;是做好数据管理和提升企业数字化发展步伐的重要工具。…