【React】React18核心源码解读

前言

本文使用 React18.2.0 的源码,如果想回退到某一版本执行git checkout tags/v18.2.0即可。如果打开源码发现js文件报ts类型错误请看本人另一篇文章:VsCode查看React源码全是类型报错如何解决。

阅读源码的过程:

  1. 下载源码

  2. 观察 package.json 使用的依赖以及构建相关的脚本

  3. 根据 核心API 寻找对应结构

    • packages/react
    • packages/react-dom
    • packages/react-reconciler
    • packages/scheduler
  4. 串联整个流程

    • React项目初始化,ReactDOM.render(React18之前)、ReactDOM.createRoot(React18)

    • 数据更新是如何触发的,this.setState,setState,forupdate

    • 基本API的使用方式

      • hooks、useState、useReducer、useId

一、ReactElement

React如何通过如下JSX代码生成DOM结构

const Element = (
	<div>123</div>
)

借助 @babel/plugin-transform-react-jsx-development 进行 Babel 编译,JSX 代码段会变成标准的 React.createElement 调用形式。官方案例链接
在这里插入图片描述
React.createElement 的作用是创建 React元素(JS对象)。

观察源码,可以发现 React 对于开发环境和生产环境的 createElement 做了不同处理。(本文观察的React18.2.0,18.3.0对此进行了小改动)

在这里插入图片描述

先观察生产环境下使用的createElementProd

在这里插入图片描述

根据传入的参数,通过ReactElement()创建一个 React 元素

在这里插入图片描述
在这里插入图片描述

开发环境下的createElementWithValidation最终也是使用ReactElement()生成React元素

在这里插入图片描述

ReactElement工厂函数用于创建一个包含类型、属性、引用、唯一标识符等信息的 React 元素(JS对象)。
生产模式下,只会创建一个简单的元素对象;而在开发模式下,会添加额外的调试信息和验证逻辑,比如 key 和 ref 验证、来源追踪等。

在这里插入图片描述

使用时直接打印组件,分别对应

在这里插入图片描述

查看typeof的标识

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
查看owner

在这里插入图片描述
在这里插入图片描述
发现ReactCurrentOwner.current类型为Fiber

在这里插入图片描述

二、Fiber

Fiber 是自 React 16 开始引入的一种新架构,在此之前采用的 Stack Reconciler同步地遍历整个组件树,一旦开始渲染,就会阻塞其他任务,直到渲染完成。Fiber 可以将渲染工作拆分为更小的任务单元,每个工作单元只渲染树中的一个节点,并允许在任务之间进行中断恢复,从而改善了这一问题。

Fiber 使用双缓存机制来管理更新。current tree 代表当前页面显示的 Fiber 树,work-in-progress tree 是当前渲染的新 Fiber 树,当新的 Fiber 树完成时,React 会将其替换为当前树。

1. Fiber工作流程

Fiber工作流程分为两个阶段,分别为 Reconciliation 阶段(调和阶段)以及 Commit 阶段(提交阶段

Reconciler 阶段「调和阶段」

该阶段会生成Fiber树,得出需要更新的节点信息,可以被中断,去处理更高优先级的任务,比如用户交互和动画。

这个阶段发生在虚拟 DOM,即 Fiber Tree 中,而不会直接影响实际的 DOM。Fiber Tree是链表结构,使用diff算法将递归遍历变成循环遍历,逐步对比每个节点的状态和属性,构建出一棵新的 Fiber 树(work-in-progress tree)。然后配合requestIdleCallback API,实现了任务的拆分、中断和恢复。

Commit 阶段「提交阶段」

一旦 work-in-progress 树构建完成,并确定了需要执行的更新,React 会进入 Commit 阶段,将这些变更应用到真实DOM 中。

当所有的 DOM 更新完成后,React 会将 work-in-progress tree 切换为 current tree,即新的 Fiber 树变成当前页面上展示的树,而之前的 current tree 会被丢弃。这种树的切换类似于双缓存的概念,即始终有一棵树在页面上渲染,而另一棵树则作为工作树进行更新。

该阶段会直接影响真实 DOM,更新操作一旦开始无法被中断,保证了 UI 的一致性和完整性。

三、Hooks

React 使用链表来管理函数组件中的 Hooks,从而确保它们在每次渲染时按照固定的顺序执行和更新。如果强行改变 Hook 的执行顺序则会报错,具体请看本人另一篇文章:为什么Hooks不能出现在判断中。

下面先以使用频率最高的useState为主线,剩余常用hook下文仍会讲述

1. resolveDispatcher

React 的 Hooks 系统通过 ReactDispatcher 来管理不同生命周期阶段的 Hook 调用。不同的渲染阶段(如初次渲染、更新渲染)会使用不同的 dispatcher 实现,以便处理对应阶段的 Hooks 逻辑。

观察常用的Hook,发现调用了 resolveDispatcher,这是一个分发器,主要用于在「函数组件」或自定义 Hook 中获取当前的 ReactDispatcher

在这里插入图片描述

查看 resolveDispatcher ,它出并返回了ReactCurrentDispatcher.current

在这里插入图片描述

继续查看ReactCurrentDispatcher.current,它只是一个简单对象,用于标记当前追踪的分发器

在这里插入图片描述

Fiber中对ReactCurrentDispatcher.current进行了「初始化」以及「更新」的处理。

在这里插入图片描述

HooksDispatcherOnMount:负责在组件初次挂载(即组件首次渲染)时处理 hooks 的调度工作。
HooksDispatcherOnUpdate: 确保在组件更新阶段,所有 hooks 能够按照正确的顺序和逻辑被执行,并且能够访问和更新之前存储的状态。

下面分别观察二者:

2. HooksDispatcherOnMount

查看最常用的useState

在这里插入图片描述

2.1 mountWordkInProgressHook()

其中 mountWordkInProgressHook() 用于在「函数组件」首次渲染创建、初始化和链接 hooks 对象到链表中并更新指向当前工作中的 hook 节点的指针。保证了 React 在管理和调度 hooks 时,能够按照正确的顺序操作每一个 hook,并在后续的更新过程中正确地访问和更新这些 hooks 的状态。

在这里插入图片描述

2.1.1 mountWordkInProgressHook 链接Hook对象流程

在这里插入图片描述

  1. 创建 Hook 对象

    • memoizedState:用于存储 hook 的状态值,比如 useState 中的状态。
    • baseState:表示 hook 的初始状态。
    • queue:用于存储更新队列,通常用在像 useState 这样的 hook 中。
    • next:指向下一个 hook 对象的引用,形成链表结构。
  2. 链接 Hook 链表
    「创建第一个 hook」 :workInProgressHook 通常为 null,会将 firstWorkInProgressHook 指向这个新创建的 hook 对象。
    「后续的 hook」:会将新创建的 hook 对象链接到当前链表的末尾workInProgressHook.next = hook),确保 hook 的执行顺序。

  3. 更新 Hook 指针:
    在每次创建完新的 hook 对象后,会更新 workInProgressHook 指针,使其指向刚刚创建并链接的 hook。确保下一次 mountWorkInProgressHook() 时,能正确地将新 hook 链接到链表的末尾。

2.2 queue

继续往下阅读代码,这一部分是对setState函数方式赋值的处理。

const [count, setCount] = useState(() => 0)

在这里插入图片描述
得到 initialState 后,将其赋值给上一步 mountWordkInProgressHook() 创建的 hook对象 的 memoizedStatebaseState

然后创建 queue 状态更新队列,其中

  • pending :存储当前挂起的更新链表,当有新的状态更新时,它们会被追加到这个链表中,等待被处理。
  • lanes :更新的优先级,NoLanes 是默认值,表示当前没有分配任何特定的优先级。
  • dispatch :一个函数引用,用于触发状态更新。调用 setState 或 dispatch,实际上就是在触发 queue.dispatch,这会触发一个新的状态更新流程。
  • lastRenderedReducer上一次渲染时使用的 reducer 函数,reducer 函数用于计算新的状态,basicStateReducer 是默认的 reducer 表示直接返回新的状态值。
  • lastRenderedState :组件上一次渲染时的状态值,用来确定是否需要触发重新渲染(如果和本次一致则不会重新渲染)。

扩展queue.lanes 在 React18 之前是通过 expirationTime 实现的,但是 React18 引入了新模型lanes,它可以「中断更新」而且「排队」、「插队」也更优。

2.3 dispatchSetState()

继续阅读代码,dispatch通过 dispatchSetState() 实现,这个函数根据当前的渲染状态决定如何处理更新,并在需要时触发组件的 「重新渲染」

代码如下:

在这里插入图片描述
在这里插入图片描述
参数

  • fiber: 当前组件对应的 Fiber 节点(组件的状态和结构)。
  • queue: 状态更新队列 UpdateQueue,存储了该组件的所有挂起的状态更新。
  • action: 用户触发的状态更新动作,可能是新的状态值或状态更新函数。

逐行分析 dispatchSetState()

  • const lane = requestUpdateLane(fiber); 获取更新的优先级
  • 更新update(状态更新的对象)
  • 处理渲染在这里插入图片描述

dispatchSetState()中还有一个很重要的函数:requestEventTime(),它用于在 React 调度事件时,根据不同的上下文返回合适的时间戳。

在这里插入图片描述
继续查看 requestEventTime()now() 的实现:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
通过代码可以发现 React 优先使用 performance.now() 提供高精度的时间戳,用于调度和优化渲染过程,对于不支持 performance.now() 的环境则使用Date.now()。二者的差别可以看本人另一篇文章Date.now()与performance.now()。

2.4 return

return就非常眼熟了,返回的数组元素分别为状态以及修改状态,这里有个小问题,可以看本人另一篇文章:useState为何返回数组而非对象

在这里插入图片描述

2.5 basicStateReducer

通过 mountStatemountReducer 可以证实 useStateuseReducer「语法糖」useReducer通过参数传递,而useState通过basicStateReducer实现状态更新。

在这里插入图片描述

查看 basicStateReducer

在这里插入图片描述
updateReducer中处理basicStateReducer

在这里插入图片描述

在这里插入图片描述

3. HooksDispatcherOnUpdate

更新

在这里插入图片描述
updateState中进行updateReducer

在这里插入图片描述

4. useCallback

经过上面的流程,此时已经对useState工作机制了解了,再来看看useCallback

4.1 挂载

同样是通过mountWorkInProgressHook() 创建、初始化和链接 hooks 对象到链表中并更新指向当前工作中的 hook 节点的指针,判断依赖数组并更新hook状态。

在这里插入图片描述

4.2 更新

如果为hook已有状态(更新渲染)、提供了有效依赖数组、依赖数组与前一次状态一致,则沿用上一次缓存的callback,否则采用传入的。

在这里插入图片描述

在这里插入图片描述

is()用于比较两个值是否完全一致

在这里插入图片描述

即使 NaN 也会视为相等

在这里插入图片描述

5. useMemo

再来看看 useMemo,不同于 useCallback 返回函数useMemo针对的是,其余逻辑一致。

在这里插入图片描述
在这里插入图片描述

6. useEffect

先来看挂载阶段的mountEffect

在这里插入图片描述

在这里插入图片描述

6.1 mountEffectImp 和 updateEffectImpl

mountEffectImpl 的任务就是挂载一个新的 useEffect,并根据依赖数组确定副作用的触发条件

在这里插入图片描述
updateEffectImpl 用于更新 useEffect

在这里插入图片描述
不论是mountEffectImpl还是updateEffectImpl最终都执行pushEffect,下面继续查看updateEffectImpl

6.1.1 pushEffect

pushEffect用于创建一个副作用对象,并将它添加到 hook 的链表中。

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

智源重磅发布 Emu3:颠覆AI多模态领域的革命性多模态大模型

在2024年10月21日&#xff0c;智源研究院正式发布了新一代的革命性多模态大模型——Emu3。这一突破标志着AI生成技术进入一个全新阶段&#xff0c;它不仅颠覆了当前的主流扩散模型&#xff08;例如Stable Diffusion&#xff09;&#xff0c;还为图像、文本和视频生成任务带来了…

HTML+CSS实现点赞效果

效果演示 HTMLCSS实现点赞效果 HTML <div class"heart-container" title"Like"><input type"checkbox" class"checkbox" id"Give-It-An-Id"><div class"svg-container"><svg viewBox&qu…

1.前提配置 关防火墙 关selinux

1.前提配置 关防火墙 关selinux 2.安装web服务程序nginx 未安装则需重新设置挂载点 若已安装&#xff0c;则查看系统中是否存在 3.当前主机添加多地址&#xff08;ip a&#xff09; 配置了三个IP地址 查看IP地址是否配置成功 4.自定义nginx配置文件通过多地址区分多网站 /…

MySQL中的优先规则

在图片的例子中&#xff0c;有两个条件&#xff1a; 第一个条件是job_id是AD_PRES并且薪水高于15,000。 第二个条件是job_id是SA_REP。 在图片中的例子有两个条件&#xff1a; 第一个条件是job_id是AD_PRES或者SA_REP。 第二个条件是薪水高于$15,000。

java如何部署web后端服务

java如何部署web后端服务 简单记录一下&#xff0c;方便后续使用。 部署流程 1.web打包 2.关掉需要升级的运行中的服务 /microservice/hedgingcustomer-0.0.1-SNAPSHOT/conf/bin/ 执行脚本 sh shutdown.sh 3.解压文件 返回到/microservice 将升级包上传到该路径&#x…

分布式ID多种生成方式

分布式ID 雪花算法&#xff08;时间戳41机器编号10自增序列号10&#xff09; 作用&#xff1a;希望ID按照时间进行有序生成 原理&#xff1a; 即一台带有编号的服务器在毫秒级时间戳内生成带有自增序号的ID,这个ID保证了自增性和唯一性 雪花算法根据结构的生成ID个数的上线时…

数字图像处理:图像分割应用

数字图像处理&#xff1a;图像分割应用 图像分割是图像处理中的一个关键步骤&#xff0c;其目的是将图像分成具有不同特征的区域&#xff0c;以便进一步的分析和处理。 1.1 阈值分割法 阈值分割法&#xff08;Thresholding&#xff09;是一种基于图像灰度级或颜色的分割方法&…

PHP短视频实训平台系统小程序源码

&#x1f3a5;短视频新纪元&#xff01;短视频实训平台系统&#xff0c;解锁创作新技能&#x1f511; &#x1f680;一键入门&#xff0c;创作无界&#x1f310; 想要玩转短视频&#xff0c;却不知从何下手&#xff1f;短视频实训平台系统是你的创意启航站&#xff01;平台内…

「C/C++」C++11 之 std::bitset 二进制数据处理模板库

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

python爬虫-爬取蛋白晶体和分子结构

文章目录 前言一、环境准备二、爬取PDB蛋白结构1.下载指定数量的随机PDB2.下载指定靶标的PDB二、从ZINC爬取小分子mol2结构1.下载指定数量的随机分子2.下载指定分子三、从ChEMBL爬取小分子信息1.下载指定ID的SMILES(测试不成功,网站变成readonly了)四、总结爬虫1.查看对应的…

【Vue】Vue3.0(十)toRefs()和toRef()的区别及使用示例

上篇文章&#xff1a;Vue】Vue&#xff08;九&#xff09;OptionsAPI与CompositionAPI的区别 &#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f916;Vue专栏&#xff1a;点击&#xff01; ⏰️创作时间&#xff1a;2024年10月15日11点13分 文章目录 toRefs()和toRe…

生成模型初认识

生成模型初认识 参考学习资料&#xff1a;李宏毅-机器学习 以下为课程过程中的简易笔记 生成模型 为什么要用生成模型&#xff1f;——创造力&#xff1a;同一个输入&#xff0c;产生不同的输出&#xff08;distribution&#xff09;&#xff0c;有一定概率发生某种随机事件…

2024 OSCAR|《开源体系建设路径模式洞察与建议》即将发布

近年来&#xff0c;开源体系建设受到高度重视&#xff0c;国家软件发展战略和“十四五”规划纲要均对开源作出重要部署&#xff0c;为我国开源体系建设和发展指明了方向。9月25日&#xff0c;工业和信息化部党组书记、部长金壮指出要加强开源体系建设&#xff0c;助推产业高质量…

大语言模型(LLM)入门级选手初学教程

链接&#xff1a;https://llmbook-zh.github.io/ 前言&#xff1a; GPT发展&#xff1a;GPT-1 2018 -->GPT-2&GPT-3&#xff08;扩大预训练数据和模型参数规模&#xff09;–> GPT-3.5&#xff08;代码训练、人类对齐、工具使用等&#xff09;–> 2022.11 ChatG…

c++初阶--string类(使用)

大家好&#xff0c;许久不见&#xff0c;今天我们来学习c中的string类&#xff0c;在这一部分&#xff0c;我们首先应该学习一下string类的用法&#xff0c;然后再试着自己去实现一下string类。 在这里&#xff0c;我使用的是这个网站来查找的string类&#xff0c;这里面的内容…

mysql--基本查询

目录 搞定mysql--CURD操作&#xff0c;细节比较多&#xff0c;不难&#xff0c;贵在多多练 1、Create--创建 &#xff08;1&#xff09;单行插入 / 全列插入 &#xff08;2&#xff09;插入否则替换 &#xff08;3&#xff09;替换 2、Retuieve--select 1&#xff09;全…

Android系統Audio hal

一.Android系統Audio hal简介 Android系统的音频硬件抽象层(HAL)是系统与硬件之间的桥梁,允许音频应用和服务访问底层音频硬件,而无需直接与硬件交互。 主要组件: 音频 HAL 接口:定义了应用和服务如何调用音频硬件的规范。典型的音频操作包括播放、录制、音量控制等。 …

day5:权限管理

一&#xff0c;权限概述 什么是权限 合适的人干合适的事情 权限的作用 根据用户的等级&#xff0c;分配对应的权利&#xff0c;完成不同用户对不同资源的相关操作。 权限的分类 r&#xff08;4&#xff09;读w&#xff08;2&#xff09;写x&#xff08;1&#xff09;执行 …

深入理解Agent:从0实现function call

Function的调用时Agent实现很重要的一步&#xff0c;只有 理解了function call这个原理才可以更好的创建Agent。 我将不使用任何langchain等框架或者coze等平台&#xff0c;从0开始构建一个可以调用function的Agent。 源代码已经上传github&#xff1a;https://github.com/as…

美摄科技云服务解决方案,方案成熟,接入简单

美摄科技作为视频处理领域的先锋&#xff0c;凭借其强大的技术实力和深厚的行业经验&#xff0c;推出了成熟的云服务解决方案&#xff0c;为轻量化视频制作开辟了全新的道路。 一、成熟方案&#xff0c;接入无忧 美摄科技云服务解决方案的最大亮点在于其成熟度和易用性。我们…