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

看透react源码之感受react的进化

写在前面

网上有许多关于react源码解读的文章,其中有很多都只是单纯贴源码,罗列变量名。其实大家都知道这个英文怎么读,直译也大概知道意思,但是这个英文在react中起到什么作用,并没有说的很通俗明白。

对于刚刚接触源码或者想要了解react实现的人来说,没有起到引导作用,一堆函数变量反而劝退了很多人。

所以打算开启一个系列的文章,用简单的代码片段代替源码,拆解react的时间分片、优先级调度、diff等核心模块,让大家一眼就能明白其中的原理。

react15为什么需要进化

react15有两大原罪,渲染阻塞和无法合并异步函数里面的setState

原罪1:同步渲染阻塞主线程

react15从setState到DOM节点渲染到页面上,整个流程都是同步的,所以如果其中某个环节占用时间特别长,就会造成主线程阻塞。

由于JS的执行是单线程的,JS线程与浏览器的其他线程互斥,如果JS线程阻塞,浏览器的渲染线程、事件线程也会相应的挂起。此时用户触发的浏览器原生事件也会无响应,造成卡顿的现象。

疑问:react15什么情况下会造成阻塞?

react15采用的是树形结构的虚拟DOM树,使用了递归方式的进行节点遍历,递归意味着虚拟DOM树的构建是一个同步的过程,只要一开始就无法中断。而且DOM节点层级越深,节点数越多,diff流程霸占JS线程的时间就越长。

当然网上都是这么说,实际上是不是真的是树形结构,是不是真的用递归的方式进行节点遍历,还是需要经过实际源码考证,为此我翻看了react@15.5.3的源码

求证1:树形结构

<div key={'最外层节点'}>{['a', 'b', 'c', 'd'].map( (v,index) =><div key={`第一层子节点 - ${v}`}><span key={`${v}的子节点`}>parentNode:{v}</span></div>)}
</div>

上面JSX代码在转换为DOM树结构时是通过树形的结构进行层层遍历

react15树形结构.png

求证2:递归遍历

这里采用伪代码的形式模拟react15的节点遍历,具体源码调用层级跨度大贴代码不好分析,有兴趣的同学可以翻看真正的源码查看具体细节

 function 构建节点(节点) {if (有子节点) return 生成子节点(节点)return 节点}function 生成子节点(children) {const 子节点列表 = []children.map(child => {子节点列表.push(构建节点(child))})return 子节点列表}function 挂载节点(node) {container.insertBefore(node)}function Render(组件, container) {const 应用根节点 = 组件()const 节点树 = 构建节点(应用根节点)挂载节点(节点树, container)}function Count(params) {return <div>1<div>}Render(<Count/>, document.querySelector('#root'))

可以看到当遍历到一个节点发现下面有子节点的时候,他会递归调用构建节点的方法继续往下构建DOM树,整个DOM树构建的过程都是同步的。

原罪2:无法合并异步函数里面的setState

除了阻塞,react15下setState的合并更新机制是以函数为单位,将函数内同步执行的setState合并,注意,是同步执行的setState,这样会出现一个问题,异步函数中的setState无法被合并。

  • 问题1:异步函数中的setState更新会以同步的形式呈现
  • 问题2:异步函数内的每一个setState都会触发一次完整的视图更新,造成性能损耗
    下面展示一下问题代码
state = { count: 0 }
setCount() {this.setState({ count: 1 })console.log(this.state.count) // 输出0,这里是正常的,state不会马上更新setTimeout(() => {this.setState({ count: 2 })console.log(this.state.count) // 输出2,state同步更新,没有被合并})
}

上面的的代码为什么会输出这样的结果,react15 的合并更新是怎么实现的呀??

卖个关子,我会在后面的系列文章中为你解答,用30行代码告诉你 react15 合并更新原理

Fiber架构下的react得到哪些提升

为解决react15的痛点,在16+版本后,react重写整个架构,为的就是实现异步可中断更新。异步可中断更新这几个字说着简单,那具体需要怎么实现呢?

回顾react15的两大痛点,我们需要解决两件事情

  1. 解决阻塞问题。
  2. 让setState在异步函数里面也能被合并。
    下面将一一解决这两个问题

解决阻塞问题

看完上面react15节点遍历的伪代码,不难发现阻塞的根源有两个

  1. 递归遍历节点树,无法中断遍历
  2. 遍历节点树会一直占用主线程,阻塞了浏览器的其他线程

解决手段1:改变树结构和节点遍历方式

react15使用了树形结构串联整棵树,这也间接导致react15采用递归+子节点for循环的方式对虚拟DOM树进行层层遍历,过程无法中断。

要实现可中断的遍历好办,不用递归,改用while遍历的话就能满足中断这个要求

但是树形结构不方便做while遍历啊,嵌套层级深,分支又多,那咋整?

把整棵树拍扁,用链表的形式描述树结构,这样我就能无需维护多余的变量记录维护遍历顺序,非常轻松的一个个遍历节点,通过while循环做遍历中断也会更加清晰

下面我用伪代码的形式简单模拟一下react16+的遍历

let 需要被遍历的幸运儿节点 = null
function 构建节点() {/**     *  ...在这里进行节点构建工作     */需要被遍历的幸运儿节点 = 需要被遍历的幸运儿节点.next
}
function 节点遍历() {while (需要被遍历的幸运儿节点 != null) {构建节点()}
}
function 调度() {需要被遍历的幸运儿节点 = react应用根节点节点遍历()
}
调度()

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

注意,需要被遍历的幸运儿节点 = 需要被遍历的幸运儿节点.next,react并不是简简单单用next去描述节点关系,我会在后面系列文章中详细描述

解决手段2:时间分片

好了,终于实现了可中断的更新,我们算是完成了半个react16了,还差一个异步,怎么做呢?那就是时间分片

时间分片顾名思义,就是设定一个固定而连续且有间隔的时间区间(好像不那么顾名思义)

什么是固定?就是我每天固定摸鱼工作8小时

什么是连续?我每天都需要上班

什么是有间隔?周末休息

react时间分片对应的就是

  1. 时间分片固定的5毫秒左右(会根据优先级有所浮动,求生欲)
  2. 分片支配着react工作的中断和开启(其实只是作用于部分工作)
  3. 分片与分片之间是有间隔的,这段间隔就是让浏览器有空闲时间去处理其他线程的任务

下面简单实现一下时间分片

下一章再讲吧,一下子写太多怕消化不了(逃

时间分片在performance中的直观体现(基本都控制在5毫秒左右)

时间分片.png

让setState在异步函数里面也能被合并

react16+对于这一块的实现,是基于整个Fiber架构的设计实现的,需要对时间分片、异步调度、lane优先级机制、state计算方式、事件系统有一定前置知识,或者能更好去理解

这里我简述下实现的原理

  1. 每一次执行setState

    a. 将此次更新的优先级关联到当前Fiber节点和根Fiber节点

    b. 执行调度函数

  2. 调度函数会先进行一个逻辑判断,判断当前应用根节点的优先级和当前已被调度的优先级是否相等

    a. 相等。是同一个函数下面的setState,可以合并更新,不重复发起协调任务

    b. 不相等。发起协调任务

这里不相等分为两种情况,一种是第一次发起调度,一种是高优先级任务进来。

如果对源码有一定了解小伙伴可能会有点点明白我这里说的是什么意思,上面说的并不完全与源码一一对应,但大概逻辑是相通的,后面我会以更详细的篇幅给大家理清楚优先级调度

宏观角度了解react的新架构

系列第一篇主要是为大家理解react16+源码做一个前置知识的铺垫,让大家对react16+的构成有一个大概的了解,下面是一张react16+的模块功能分布图

react.png

Scheduler

Scheduler主要负责react的任务调度,其中包括分片调度和优先级调度

  • 分片调度的主要任务是负责reconcile (render)阶段能够间断执行节点遍历任务

  • 优先级调度主要是为了将react任务划分为多种优先级类型,能够实现高优先级任务快速响应

Reconciler

Reconciler主要负责Fiber节点的构建和创建相应的副作用

  • state计算在引入了优先级机制后,并不是简单的将state计算覆盖,其中关联到低优先级任务重启的逻辑

  • diff就是通过遍历新旧Fiber树,找出需要增删改的节点

  • 副作用创建将需要增删改的节点以位与运算的形式记录到Fiber节点的flags属性上,等待commit阶段清除这些副作用(副作用包含但不限于节点增删改,还有useEffect执行,ref更新等等的副作用)

Renderer

Renderer (commit)阶段做的事情就是清除副作用,然后开启下一轮的调度

以上就是react的基本构成和各个模块的职责。后续为了更方便进行解读,我会用render阶段代指Reconciler,用commit阶段代指Renderer

写在最后

本文主要简述了react的进化历程和新react架构的基本构成。下一篇我会讲讲react的时间分片,同时会结合react的任务去模拟一个时间分片的运行过程。

上文所述如果有说的不对的,望各位大佬可以包涵指正。如果有不懂的,可以把疑问点提出来,我会逐一解答。每一次交流的过程都是一次思想和学习的碰撞,大家可以尽情diss

相关文章:

看透react源码之感受react的进化

写在前面 网上有许多关于react源码解读的文章&#xff0c;其中有很多都只是单纯贴源码&#xff0c;罗列变量名。其实大家都知道这个英文怎么读&#xff0c;直译也大概知道意思&#xff0c;但是这个英文在react中起到什么作用&#xff0c;并没有说的很通俗明白。 对于刚刚接触…...

【最优化理论】线性规划

文章目录什么是线性规划&#xff08;Linear Programming&#xff0c;LP&#xff09;&#xff1f;线性规划的标准形式非标准形LP模型转化为标准形LP模型基本概念基本解&基矩阵&基变量&非基变量基本可行解&可行基矩阵&非退化的基本可行解&退化的基本可行…...

数据库测试的认知和分类

数据库测试的认知和分类 目录&#xff1a;导读 系统测试 集成测试 单元测试 功能测试 数据库性能 性能优化分4部分 安全测试 现在的软件系统&#xff0c;尤其是业务应用系统&#xff0c;后台都连接着一个数据库。数据库中存储了大量的数据&#xff0c;数据库的设计是否…...

MQ中间件概念一览

一、概述 1. 大多应用中&#xff0c;可通过消息服务中间件来提升系统异步通信、扩展解耦能力 2. 消息服务中两个重要概念&#xff1a; 消息代理&#xff08;message broker&#xff09;和目的地&#xff08;destination&#xff09; 当消息发送者发送消息以后&#xff0c;将由…...

爱尔兰公司注册要求及条件

简介&#xff1a; 爱尔兰是一个高度发达的资本主义国家&#xff0c;也是欧盟、经济合作与发展组织、世界贸易组织和联合国的成员国。并且也是世界经济发展速度快的国家之一&#xff0c;因经济发达赢得了“欧洲小虎”的美誉。总体来看&#xff0c;爱经济发展势头趋稳&#xff0c…...

Java中如何打印对象内存地址?

先看一个简单的程序&#xff0c;一般我们打印对象&#xff0c;大部分是下面的情况&#xff0c;可能会重写下toString()方法&#xff0c;这个另说 Frolan frolan new Frolan(); System.out.println(frolan);// 输出结果 com.test.admin.entity.Frolan2b80d80f这个结果其实是调…...

CF1707E Replace

题目描述 给定一个长为 nnn 的序列 a1,…,ana_1,\ldots,a_na1​,…,an​&#xff0c;其中对于任意的 iii 满足 1≤ai≤n1 \leq a_i \leq n1≤ai​≤n。 定义一个二元组函数如下&#xff1a; f((l,r))(min⁡{al,…,ar},max⁡{al,…,ar})(l≤r)f((l,r))(\min\{a_l,\ldots,a_r\}…...

【Hello Linux】Linux工具介绍 (make/makefile git)

作者&#xff1a;小萌新 专栏&#xff1a;Linux 作者简介&#xff1a;大二学生 希望能和大家一起进步&#xff01; 本篇博客简介&#xff1a;介绍Linux的常用工具make/makefile git Linux项目自动化构建工具 – make/Makefile 背景 会不会写Makefile 从侧面说明了一个人是否具…...

享元模式flyweight

享元模式属于结构型模式。享元模式是池技术的重要实现方式&#xff0c;它可以减少重复对象的创建&#xff0c;使用缓存来共享对象&#xff0c;从而降低内存的使用。细粒度的对象其状态可以分为两种&#xff1a;内部状态和外部状态。应用场景系统存在大量相似或相同的对象。外部…...

Pulsar

一、简介Apache Pulsar是Apache软件基金会顶级项目&#xff0c;是下一代云原生分布式消息流平台&#xff0c;集消息、存储、轻量化函数式计算为一体&#xff0c;采用计算与存储分离架构设计&#xff0c;支持多租户、持久化存储、多机房跨区域数据复制&#xff0c;具有强一致性、…...

项目介绍 + 定长内存池设计及实现

你好&#xff0c;我是安然无虞。 文章目录项目介绍当前项目做的是什么?技术栈内存池是什么?池化技术内存池内存池主要解决的问题malloc定长内存池学习目的定长内存池设计项目介绍 当前项目做的是什么? 这个项目是实现一个高并发的内存池, 它的原型是 Google 的一个开源项…...

Linux--线程安全的单例模式--自旋锁--0211

1. 线程安全的单例模式 1.1 什么是单例模式 某些类, 只应该具有一个对象(实例), 就称之为单例. 1.1.1 懒汉方式实现单例模式 以上篇博文的线程池为例 Liunx--线程池的实现--0208 09_Gosolo&#xff01;的博客-CSDN博客 实现懒汉模式首先要先将构造函数私有化&#xff0c;…...

图文解说S参数(进阶篇)

S参数是RF工程师/SI工程师必须掌握的内容&#xff0c;业界已有多位大师写过关于S参数的文章&#xff0c;即便如此&#xff0c;在相关领域打滚多年的人&#xff0c; 可能还是会被一些问题困扰着。你懂S参数吗? 图文解说S参数&#xff08;基础篇&#xff09; 请继续往下看...台湾…...

Sentinel源码阅读

基础介绍 Sentinel 的使用可以分为两个部分: 核心库&#xff08;Java 客户端&#xff09;&#xff1a;不依赖任何框架/库&#xff0c;能够运行于 Java 8 及以上的版本的运行时环境&#xff0c;同时对 Dubbo / Spring Cloud 等框架也有较好的支持&#xff08;见 主流框架适配&…...

2023年浙江食品安全管理员考试真题题库及答案

百分百题库提供食品安全管理员考试试题、食品安全管理员考试预测题、食品安全管理员考试真题、食品安全管理员证考试题库等&#xff0c;提供在线做题刷题&#xff0c;在线模拟考试&#xff0c;助你考试轻松过关。 一、判断题 7.&#xff08;重点&#xff09;《餐饮服务食品安全…...

Webstorm 代码没有提示,uniapp 标签报错

问题 项目是用脚手架创建的&#xff1a; vue create -p dcloudio/uni-preset-vue my-project 打开之后&#xff0c;添加view标签警告报错的。代码也没有提示&#xff0c;按官方说法&#xff1a;CLI 工程默认带了 uni-app 语法提示和 5App 语法提示。 但是我这里就是有问题。…...

MySQL-Innodb引擎事务原理

文章目录1.事务介绍2 事务特性3. 事务的实现原理4 redo log 保证持久性5 undo log 保证原子性6 MVCC 概念6.1 隐藏字段6.2 版本链6.3 ReadView6.3.1readview 版本控制规则7 隔离性 实现7.2 隔离性- REPEATABLE READ 可重复读下8 一致性1.事务介绍 事务是一组操作的集合&#xf…...

Linux操作系统学习(了解环境变量)

文章目录环境变量初识除了上述介绍的PATH&#xff0c;还有一些常见的环境变量如&#xff1a;查看环境变量方法 &#xff1a;环境变量的基本概念&#xff1a;本地变量&#xff1a;环境变量初识 环境变量解释起来比较抽象&#xff0c;先看示例&#xff1a; #include <stdio.…...

数据分析思维(六)|循环/闭环思维

循环/闭环思维 1、概念 在很多的分析场景下&#xff0c;我们需要按照一套流程反复分析&#xff0c;而不是进行一次性的分析&#xff0c;也就是说这套流程的结果会成为该流程的新一次输入&#xff0c;从而形成一个闭环&#xff0c;此时的分析思维我们称之为循环/闭环思维。 常…...

C++:类和对象(下)

文章目录1 再谈构造函数1.1 构造函数体赋值1.2 初始化列表1.3 explicit关键字2 static成员2.1 概念2.2 特性3 友元3.1 友元函数&#xff08;流插入&#xff08;<<&#xff09;及流提取&#xff08;>>&#xff09;运算符重载&#xff09;3.2 友元类4 内部类5 匿名对…...

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…...

ES6从入门到精通:前言

ES6简介 ES6&#xff08;ECMAScript 2015&#xff09;是JavaScript语言的重大更新&#xff0c;引入了许多新特性&#xff0c;包括语法糖、新数据类型、模块化支持等&#xff0c;显著提升了开发效率和代码可维护性。 核心知识点概览 变量声明 let 和 const 取代 var&#xf…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

从深圳崛起的“机器之眼”:赴港乐动机器人的万亿赛道赶考路

进入2025年以来&#xff0c;尽管围绕人形机器人、具身智能等机器人赛道的质疑声不断&#xff0c;但全球市场热度依然高涨&#xff0c;入局者持续增加。 以国内市场为例&#xff0c;天眼查专业版数据显示&#xff0c;截至5月底&#xff0c;我国现存在业、存续状态的机器人相关企…...

定时器任务——若依源码分析

分析util包下面的工具类schedule utils&#xff1a; ScheduleUtils 是若依中用于与 Quartz 框架交互的工具类&#xff0c;封装了定时任务的 创建、更新、暂停、删除等核心逻辑。 createScheduleJob createScheduleJob 用于将任务注册到 Quartz&#xff0c;先构建任务的 JobD…...

什么是库存周转?如何用进销存系统提高库存周转率?

你可能听说过这样一句话&#xff1a; “利润不是赚出来的&#xff0c;是管出来的。” 尤其是在制造业、批发零售、电商这类“货堆成山”的行业&#xff0c;很多企业看着销售不错&#xff0c;账上却没钱、利润也不见了&#xff0c;一翻库存才发现&#xff1a; 一堆卖不动的旧货…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

【碎碎念】宝可梦 Mesh GO : 基于MESH网络的口袋妖怪 宝可梦GO游戏自组网系统

目录 游戏说明《宝可梦 Mesh GO》 —— 局域宝可梦探索Pokmon GO 类游戏核心理念应用场景Mesh 特性 宝可梦玩法融合设计游戏构想要素1. 地图探索&#xff08;基于物理空间 广播范围&#xff09;2. 野生宝可梦生成与广播3. 对战系统4. 道具与通信5. 延伸玩法 安全性设计 技术选…...

LangChain知识库管理后端接口:数据库操作详解—— 构建本地知识库系统的基础《二》

这段 Python 代码是一个完整的 知识库数据库操作模块&#xff0c;用于对本地知识库系统中的知识库进行增删改查&#xff08;CRUD&#xff09;操作。它基于 SQLAlchemy ORM 框架 和一个自定义的装饰器 with_session 实现数据库会话管理。 &#x1f4d8; 一、整体功能概述 该模块…...

基于IDIG-GAN的小样本电机轴承故障诊断

目录 🔍 核心问题 一、IDIG-GAN模型原理 1. 整体架构 2. 核心创新点 (1) ​梯度归一化(Gradient Normalization)​​ (2) ​判别器梯度间隙正则化(Discriminator Gradient Gap Regularization)​​ (3) ​自注意力机制(Self-Attention)​​ 3. 完整损失函数 二…...