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

news2025/7/19 16:52:19

写在前面

网上有许多关于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

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

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

相关文章

推荐系统常见算法分类

文章目录1.基本分类2.基于算法思想的分类3.基于应用问题的分类该系列历史文章&#xff1a; 1.推荐系统最通俗介绍 资料整理&#xff0c;来源于北大刘宏志教授讲座内容。 1.基本分类 常见的推荐系统算法分类如下&#xff1a; 算法思想 基于人口统计学、基于内容、协同过滤、基…

Django练习

目录 基础命令 一、新建项目 二、配置 三、运行 Bootstrap下载 jQuery下载 基础命令 #创建项目 django-admin startproject [项目名称] #创建app应用 python manage.py startapp [app名称] #运行 python manage.py runserver [端口号] #创建数据模型和数据表结构 python…

HTML PDF 查看器--RAD PDF 3.33 FOR ASP.NET

RAD PDF 的主要特点 基于 HTML 的 PDF 阅读器 客户端 PDF 编辑器 功能丰富的 PDF 表单填写器 交互式 PDF 表单设计器 保护 PDF 内容 签署和认证 PDF 文件 广泛的兼容性 & 在您的服务器上 将 PDF 集成到您的工作流程中 使用 ASP.NET 或 ASP.NET Core / 5 / 6 破解版RAD PDF…

pytorch深度学习实战lesson27

第二十七课 批量归一化 下面来讲批量归一化&#xff0c;现在几乎所有主流的卷积神经网络都是或多或少的用了批量归一化这个层。虽然我们之前看到的那些层比如 pooling 或 convolution&#xff0c;其实他们在80年代就出现过了&#xff0c;只是现在我们把它做得更深更大。批量归一…

Kanzi Shader入门

1. 版本 kanzi默认支持Opengl ES 2.0&#xff0c;在qnx平台可以支持到ES 3.0 2. 着色器 kanzi只支持【顶点着色器】和【片段着色器】 3. kanzi studio 无法直接使用shader&#xff0c;需要通过画刷和材质间接使用 在【普通节点】上设置背景画刷-【材质画刷】在【材质画刷…

原生Android 以面向对象的方式操作canvas

Android 自定义view 用canvas去画图形, 都是以面向过程的方式去一笔一笔的画, 而且画的图形也不能支持添加事件, 而html, js在这方面有大量的封装好的canvas框架, 很奇怪的是android上我也没有搜到类似的封装框架, 我只是个web前端开发者, 可能是我对android不了解没有搜索到&a…

Nodejs中包的介绍及npm安装依赖包的多种方法

文章目录1 包的介绍1.1 什么是包1.2 包的来源1.3 为什么需要包1.4 从哪里下载包1.5 如何下载包2 npm2.1 npm安装依赖包2.2 装包后多了哪些文件2.3 安装指定版本的包1 包的介绍 1.1 什么是包 Nodejs中的第三方模块又叫做包 就像电脑和计算机指的是相同的东西&#xff0c;第三…

Wireshark Ethernet and ARP 实验—Wireshark Lab: Ethernet and ARP v7.0

Wireshark Lab: Ethernet and ARP v7.0 1. Capturing and analyzing Ethernet frames 清除浏览器缓存 使用wireshark抓包并请求网页 修改“捕获数据包列表”窗口&#xff0c;仅显示有关 IP 以下协议的信息。 抓包干扰较多&#xff0c;故分析作者的数据包回答下列问题 包含…

Apollo 应用与源码分析:Monitor监控 - 基本概念与入口分析

Monitor 系统监控 目录 基本概念 代码结构分析 整体逻辑分析 基本概念 整体分类 该模块包含用于检查硬件状态和监视系统运行状况的代码等系统级软件。 在Apollo 5.5中&#xff0c;监视模块现在执行以下检查: 运行模块状态监控数据完整性监控数据频率监视系统运行状况(例…

springboot 点滴(3)springboot ThreadLocal实现单机权限认证

Springboot中权限认证的基本方案&#xff1a; 可以对URL进行HandlerInterceptor拦截&#xff0c;然后权限验证。 Client登录成功后&#xff0c;每次发送请求时&#xff0c;会将token等信息存放到header中。 Server收到请求&#xff0c;在HandlerInterceptor中从header获取用户…

3.4、可靠传输

3.4、可靠传输 3.4.1、基本概念 使用差错检测技术\color{red}差错检测技术差错检测技术&#xff08;例如循环冗余校验 CRC )&#xff0c;接收方的数据链路层就可检测出帧在传输过程中是否产生了误码\color{red}误码误码&#xff08;比特错误)。 数据链路层向上层提供的服务类…

hive中连续N天登录问题、topN问题、拉链表实现

一、连续N天登录问题 一般采用开窗函数来实现 首先需要用到窗口函数的向下取值 窗口函数lead 功能&#xff1a;用于从当前数据中基于当前行的数据向后偏移取值 语法&#xff1a;lead(colName&#xff0c;N&#xff0c;defautValue) colName&#xff1a;取哪一列的值 N&#xf…

RemObjects Remoting SDK for Delphi

RemObjects Remoting SDK for Delphi 远程处理SDK是一个框架&#xff0c;允许您创建使用服务公开性能的服务器。然后&#xff0c;可以通过网络远程访问这些解决方案&#xff0c;这些解决方案可以通过运行在大量语言和平台上的客户端软件来实现。 在当今时代&#xff0c;许多程序…

【人才盘点九宫格】你还不来学一学人才九宫格,知道你在领导心目中是属于哪一类人才吗?

文章目录一、九宫格的维度——绩效、能力、潜力1.1 维度一&#xff1a;绩效。1.2 维度二&#xff1a;能力。1.3 维度三&#xff1a;潜力。二、九宫格分类2.1 经典九宫格&#xff1a;绩效-能力九宫格2.2 高潜九宫格&#xff1a;绩效-潜力九宫格2.2.1 高潜九宫格图示2.2.2 高潜九…

SRV1:拥有一个阿里云服务器

1.1 简介 平台&#xff1a; 阿里云   时间&#xff1a; 2022.11.25   类型&#xff1a; 轻量应用服务器2核2G   链接&#xff1a; https://www.aliyun.com/daily-act/ecs/activity_selection?utm_contentse_1013075595 1.2 说明 本文购买的是阿里云的 轻量应用服务器2…

NFT 的洗盘交易,真的赚钱吗?

Nov. 17&#xff0c;Hanson Data Source&#xff1a; Footprint Analytics Wash Trade Analysis 洗盘交易造成的虚假交易是准确评估 NFT 项目、交易所和整个行业的最大障碍之一。 这份报告将会分析X2Y2市场的洗盘交易者如何从洗盘交易和代币质押中获励。同时分析了前10交易量…

3.3、差错检测

3.3、差错检测 3.3.1、比特差错 比特差错\color{red}比特差错比特差错 实际的通信链路都不是理想的&#xff0c;比特在传输过程中可能会产生差错: 111 可能会变成 000 ,而 000 也可能变成 111 。 比特流在传输过程中由于受到各种干扰&#xff0c;就可能会出现比特差错&#…

K8S基础知识学习

目录 一、什么是 Kubernetes &#xff1f; Kubernetes是Google在2014年开源的一个容器集群管理系统&#xff0c;Kubernetes简称K8S。 K8S用于容器化应用程序的部署&#xff0c;扩展和管理。 K8S提供了容器编排&#xff0c;资源调度&#xff0c;弹性伸缩&#xff0c;部署管理&a…

详解设计模式:简单工厂模式

简单工厂模式&#xff08;Smiple Factory Pattern&#xff09;&#xff1a;定义一个工厂类&#xff0c;他可以根据参数的不同返回不同类的实例&#xff0c;被创建的实例通常都具有共同的父类&#xff0c;简单工厂模式也被称为静态工厂模式。 &#xff5e; 本篇内容包括&#xf…

若依(Ruoyi-Vue-Plus版)——1.登录(SaToken)

这里学习一下若依框架的一个扩展版本&#xff1a; RuoYi-Vue-Plus: 后台管理系统 重写RuoYi-Vue所有功能 集成 Sa-TokenMybatis-PlusJacksonXxl-JobSpringDocHutoolOSS 定期同步 (gitee.com) 官方文档&#xff1a;文档预览 - Gitee.com 项目有关SaToken登录写在最后&#xff0c…