【React】React18核心源码解读
前言
本文使用 React18.2.0
的源码,如果想回退到某一版本执行git checkout tags/v18.2.0
即可。如果打开源码发现js文件报ts类型错误请看本人另一篇文章:VsCode查看React源码全是类型报错如何解决。
阅读源码的过程:
-
下载源码
-
观察
package.json
使用的依赖以及构建相关的脚本 -
根据
核心API
寻找对应结构- packages/react
- packages/react-dom
- packages/react-reconciler
- packages/scheduler
-
串联整个流程
-
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对象流程
-
创建 Hook 对象
- memoizedState:用于存储 hook 的状态值,比如 useState 中的状态。
- baseState:表示 hook 的初始状态。
- queue:用于存储更新队列,通常用在像 useState 这样的 hook 中。
- next:指向下一个 hook 对象的引用,形成链表结构。
-
链接 Hook 链表
「创建第一个 hook」 :workInProgressHook
通常为 null,会将firstWorkInProgressHook
指向这个新创建的 hook 对象。
「后续的 hook」:会将新创建的 hook 对象链接到当前链表的末尾
(workInProgressHook.next = hook
),确保 hook 的执行顺序。 -
更新 Hook 指针:
在每次创建完新的 hook 对象后,会更新workInProgressHook
指针,使其指向刚刚创建并链接的 hook。确保下一次mountWorkInProgressHook()
时,能正确地将新 hook 链接到链表的末尾。
2.2 queue
继续往下阅读代码,这一部分是对setState
函数方式赋值的处理。
const [count, setCount] = useState(() => 0)
得到 initialState
后,将其赋值给上一步 mountWordkInProgressHook()
创建的 hook对象 的 memoizedState
和 baseState
。
然后创建 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
通过 mountState
和 mountReducer
可以证实 useState
是 useReducer
的 「语法糖」,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 的链表中。
相关文章:

【React】React18核心源码解读
前言 本文使用 React18.2.0 的源码,如果想回退到某一版本执行git checkout tags/v18.2.0即可。如果打开源码发现js文件报ts类型错误请看本人另一篇文章:VsCode查看React源码全是类型报错如何解决。 阅读源码的过程: 下载源码 观察 package…...

部署私有仓库以及docker web ui应用
官方地址:https://hub.docker.com/_/registry/tags 一、拉取registry私有仓库镜像 docker pull registry:latest 二、运⾏容器 docker run -itd -v /home/dockerdata/registry:/var/lib/registry --name "pri_registry1" --restartalways -p 5000:5000 …...

DAY57WEB 攻防-SSRF 服务端请求Gopher 伪协议无回显利用黑白盒挖掘业务功能点
知识点: 1、SSRF-原理-外部资源加载 2、SSRF-利用-伪协议&无回显 3、SSRF-挖掘-业务功能&URL参数 SSRF-原理&挖掘&利用&修复 漏洞原理:SSRF(Server-Side Request Forgery:服务器端请求伪造) ,一种由攻击者构造形成由服务…...
光盘刻录大文件时分卷操作
可以使用 split 命令来将大文件 finetune.tar 分卷为适合光盘大小的文件片段,然后在离线服务器上合并这些分卷文件。以下是具体的操作步骤: 步骤1:分卷文件 假设你的文件 finetune.tar 大小为35GB,并且你想分卷为每个4.7GB&…...
Kafka系列之:生产者性能调优
Kafka系列之:生产者性能调优 一、producer.type二、request.required.acks三、max.request.size四、batch.size五、buffer.memory一、producer.type 在Kafka中,producer.type是一个配置属性,用于指定Producer的类型。它有两个可能的值: sync:同步发送模式。当设置为sync时…...

【linux】进程创建与进程终止
🔥个人主页:Quitecoder 🔥专栏:linux笔记仓 目录 01.进程创建02.进程终止异常终止如何终止exit()_exit() 01.进程创建 #include <unistd.h> pid_t fork(void);返回值:自进程中返回0,父进程返回子进…...

QT的文件操作类 QFile
QFile 是 Qt 框架中用于文件处理的一个类。它提供了读取和写入文件的功能,支持文本和二进制文 件。 QFile 继承自 QIODevice ,因此它可以像其他IO设备一样使用。 主要功能 文件读写: QFile 支持打开文件进行读取或写入操作文件信息&#x…...
java项目篇-用户脱敏展示
用户敏感信息脱敏展示 定义手机号和证件号的 Jackson 自定义序列化器,并在对应需要脱敏的敏感字段上指定自定义序列化器。在进行指定的需要脱敏的字段(身份证号,手机号,银行卡号等)序列化的时候,该字段自动…...
《C++计算引擎:驱动高效计算的强大动力》
在当今数字化时代,高效的计算能力是推动科技进步和创新的关键。而 C作为一种强大的编程语言,在构建高性能计算引擎方面发挥着重要作用。本文将深入探讨 C计算引擎的特点、优势以及在不同领域的应用,带您领略 C在计算领域的独特魅力。 一、C计…...

Linux的hadoop集群部署
1.hadoop是一个分布式系统基础架构,主要解决海量数据额度存储与海量数据的分析计算问题 hdfs提供存储能力,yarn提供资源管理能力,MapReduce提供计算能力 2.安装 一:调整虚拟机内存,4G即可 二:下载安装包 网址:https://mirrors.aliyun.com/apache/hadoop/common/hadoop-3.4.0/…...

请问:ESModule 与 CommonJS 的异同点是什么?
前言 本篇文章不会介绍模块的详细用法,因为核心是重新认识和理解模块的本质内容是什么,直奔主题,下面先给出最后结论,接下来在逐个进行分析。 ECMAScript Module 和 CommonJS 的相同点: 都拥有自己的缓存机制&#…...
【数据结构与算法】力扣 59. 螺旋矩阵 II
题目描述 给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。 示例 1: 输入: n 3 输出: [[1,2,3],[8,9,4],[7,6,5]]示例 2: 输入:…...

HarmonyOS Next模拟器异常问题及解决方法
1、问题1:Failed to get the device apiVersion. 解决方法:关闭模拟器清除用户数据重启...

求最大公约数(c语言)
先看题👇 我这里介绍的方法:辗转相除法: 最大公约数: 最大公约数是指同时能整除俩个或更多整数的最大正整数。 欧几里得算法就是求最大公约数的算法 求最大公约数涉及到一个数学原理的转换: 俩个数的最大公约数等于其中一个数和…...

Android Camera2在textureView中的预览和拍照
Camera2预览和拍照 1、Camera2相机模型2、Camera2的重要类3、Camera2调用流程4、Camera2调用实现 1)定义TextureView作为预览界面2)设置相机参数3)开启相机4)开启相机预览5)实现PreviewCallback6)拍照 1、Camera2相机模型 解释上诉示意图,假如想要同时拍摄两张不同…...
Redis的缓存问题
缓存雪崩 定义:缓存雪崩是指在某个时间段内,缓存中的大量数据同时失效或者大量的请求集中到某一个时间点发生,导致数据库压力骤增,甚至引起服务崩溃的现象。 原因:通常是由于缓存中的大量数据同时过期或者大量的请求集…...

C语言小游戏--猜数字
游戏过程: 由电脑随机在某个范围内生成一个数字,玩家猜数字并且输入,电脑判断是否正确,正确则游戏结束,错误则给出提示,直到玩家所给的答案正确为止 思路分析: 1.生成随机数 2.玩家可以多次…...
代理IP在爬虫中的作用是什么?
在爬虫中,代理IP的主要作用包括以下几个方面: 防止IP被封禁:每个网站都有反爬机制,会记录并封禁同一个IP地址的频繁请求。使用代理IP可以让爬虫更换源头,减少被目标网站识别为恶意爬虫的风险。 提高抓取效率ÿ…...

卡尔曼讲解与各种典型进阶MATLAB编程(专栏目录,持续更新……)
专栏链接:https://blog.csdn.net/callmeup/category_12574912.html 文章目录 专栏介绍重点文章卡尔曼滤波的原理卡尔曼滤波的例程 进阶MATLAB编程后续更新 专栏介绍 本专栏旨在深入探讨卡尔曼滤波及其在各类应用中的实现,尤其是通过MATLAB编程进行的典…...

Java项目-基于Springboot的智慧养老平台项目(源码+文档).zip
作者:计算机学长阿伟 开发技术:SpringBoot、SSM、Vue、MySQL、ElementUI等,“文末源码”。 开发运行环境 开发语言:Java数据库:MySQL技术:SpringBoot、SpringClud、Vue、Mybaits Plus、ELementUI工具&…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...

以下是对华为 HarmonyOS NETX 5属性动画(ArkTS)文档的结构化整理,通过层级标题、表格和代码块提升可读性:
一、属性动画概述NETX 作用:实现组件通用属性的渐变过渡效果,提升用户体验。支持属性:width、height、backgroundColor、opacity、scale、rotate、translate等。注意事项: 布局类属性(如宽高)变化时&#…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...

新能源汽车智慧充电桩管理方案:新能源充电桩散热问题及消防安全监管方案
随着新能源汽车的快速普及,充电桩作为核心配套设施,其安全性与可靠性备受关注。然而,在高温、高负荷运行环境下,充电桩的散热问题与消防安全隐患日益凸显,成为制约行业发展的关键瓶颈。 如何通过智慧化管理手段优化散…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...

Python Ovito统计金刚石结构数量
大家好,我是小马老师。 本文介绍python ovito方法统计金刚石结构的方法。 Ovito Identify diamond structure命令可以识别和统计金刚石结构,但是无法直接输出结构的变化情况。 本文使用python调用ovito包的方法,可以持续统计各步的金刚石结构,具体代码如下: from ovito…...

LabVIEW双光子成像系统技术
双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制,展现出显著的技术优势: 深层组织穿透能力:适用于活体组织深度成像 高分辨率观测性能:满足微观结构的精细研究需求 低光毒性特点:减少对样本的损伤…...