React原理之React整体渲染流程

news2024/11/25 2:37:11

前置知识:深度优先搜索(DFS)、Fiber 节点

在上一篇 React原理篇之 React 整体架构解读中,提到了 Fiber 架构中的几个核心概念:

  • Scheduler(调度器):根据任务的优先级安排任务执行顺序。
  • Reconciler(协调器):根据新旧虚拟 DOM 树的差异确定需要更新的部分。
  • Renderer(渲染器):将更新的虚拟 DOM 转换为实际的 UI 输出。

这三个组件共同工作提供了 React 的高效和灵活的渲染机制,那么他们具体是怎么渲染的呢?

React 的渲染的两个阶段

React 的渲染流程分为两个阶段:

  • render 阶段:Reconciler 的工作阶段,这个阶段会调用组件的 render 方法
  • commit 阶段:Renderer 的工作阶段,可以类比 git commit 提交,这个阶段会渲染具体的 UI。

在这里插入图片描述

先以这个两个阶段的整体工作流程举例:

export default function App() {
	const [count, setCount] = useState(0);

	const handleIncrement = () => {
		setCount((prevCount) => prevCount + 1);
	};
	return (
		<div>
			<h3>{count}</h3>
			<button onClick={handleIncrement}>点击加一</button>
		</div>
	);
}

在这里插入图片描述

如上图所示,当用户点击按钮更新 count,Scheduler 先进行任务的协调,当 Scheduler 调度完成后,将任务交给 Reconciler,Reconciler 就需要计算出新的 UI,最后就由 Renderer 同步进行渲染更新操作。

Scheduler 和 Reconciler 的工作流程是可以随时被以下原因中断:

  • 有其他更高优先级的任务需要执行
  • 当前的 time slice 没有剩余的时间
  • 发生了其他错误

Scheduler 和 Reconciler 的的工作是在内存里进行的,不会更新用户界面,因此即使工作流程反复被中断,用户也不会看到更新不完全的 UI。

由于 Scheduler 和 Reconciler 都是平台无关的,所以React为他们单独发了一个包react-Reconciler

调度器 Scheduler

上篇文章提到,Fiber 和 Scheduler 都是 React16 引入的。Scheduler 是用来根据任务的优先级安排任务执行顺序的。

其实部分浏览器的原生 API 已经实现了,即requestIdleCallback

https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback

但是由于 浏览器兼容性触发频率受很多因素影响而不稳定 等问题,React放弃使用浏览器原生的 API,React实现了功能更完备的requestIdleCallbackPolyfill,即Scheduler。除了在空闲时触发回调的功能外,Scheduler还提供了多种调度优先级供任务设置。

另外,Scheduler是独立于React的库,可以用来实现任务调度,而不只是在 React 中使用。

注:Polyfill 是指用于在旧版本浏览器中实现新标准 API 的代码填充(或称垫片)。它通常用于解决旧版本浏览器不支持新特性或 API 的问题。

协调器 Reconciler 与 Render 阶段

Reconciler 实现可中断的循环

Reconciler 根据新旧虚拟 DOM 树的差异确定需要更新的部分。

上一篇文章说到,在 React15 中Reconciler是递归处理虚拟 DOM 的。而 React16 中,更新工作从递归变成了可以中断的循环过程。

  • 每次循环都会调用shouldYield判断当前是否有剩余时间。如果当前浏览器帧没有剩余时间,shouldYield会中止循环,直到浏览器有空闲时间后再继续遍历。
  • Reconciler 与 Renderer 不再是交替工作。当 Scheduler 将任务交给 Reconciler 后, Reconciler 会为变化的虚拟 DOM 打上代表增/删/更新的标记,整个 Scheduler 与 Reconciler 的工作都在内存中进行。只有当所有组件都完成 Reconciler 的工作,才会统一交给 Renderer。

Render 阶段

类组件或者函数组件本身就是在 render 阶段被调用的。在源码中,render 阶段开始于performSyncWorkOnRootperformConcurrentWorkOnRoot方法的调用,这取决于本次更新是同步更新还是异步更新。

  1. performSyncWorkOnRoot:同步模式

  2. performConcurrentWorkOnRoot:并发模式

// performSyncWorkOnRoot会调用该方法
function workLoopSync() {
	while (workInProgress !== null) {
		performUnitOfWork(workInProgress);
	}
}

// performConcurrentWorkOnRoot会调用该方法
function workLoopConcurrent() {
	while (workInProgress !== null && !shouldYield()) {
		performUnitOfWork(workInProgress);
	}
}

对于以上代码的注释:

workInProgress : 当前已创建的workInProgress fiber,即在内存中构建的Fiber树(具体 fiber 双缓存相关后面文章细讲)

shouldYield: 如果当前浏览器帧没有剩余时间,shouldYield会中止循环,直到浏览器有空闲时间后再继续遍历。(可以看到上面两种方法的区别是是否调用 shouldYield)

performUnitOfWork: 创建下一个Fiber节点并赋值给workInProgress,并将workInProgress与已创建的Fiber节点连接起来构成Fiber树

可以看到上面两种方法主要都是在执行performUnitOfWork,下面我们详细看一下performUnitOfWork

performUnitOfWork 方法

performUnitOfWork 方法的工作流程可以分为两个阶段:“ 递 ” 和 “ 归 ”。

“递阶段 —— beginWork”

作用:传入当前Fiber节点,创建子Fiber节点

首先从rootFiber开始向下深度优先遍历。为遍历到的每个Fiber节点调用beginWork方法(此方法后续详细介绍),该方法会根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来。

当遍历到叶子节点(即没有子组件的组件)时就会进入“归”阶段。

“归阶段 —— complateWork”

作用:收集一些副作用。

在“归”阶段调用 completeWork 处理Fiber节点,主要是收集一些副作用。(此方法后续详细介绍)

当某个Fiber节点执行完completeWork,如果其存在同级Fiber节点(即fiber.sibling !== null),会进入其同级Fiber的“递”阶段。

如果不存在同级Fiber,会进入父级Fiber的“归”阶段。

“递”和“归”阶段会交错执行直到“归”到rootFiber。至此,render阶段的工作就结束了。

图示 “ 递 ” 和 “ 归 ”

先看一个简单的:

在这里插入图片描述

稍复杂的 fiber 节点。注意 beginWork 和 complateWork 的顺序:
在这里插入图片描述

渲染器 Renderer 与 commit 阶段

render 阶段完成后,开启commit阶段工作流程,Renderer 在此阶段工作。

与 render 阶段可以被打断不同的是,commit 阶段是不可以被打断的,一旦开始就会同步执行直到完成渲染工作。

渲染器 Renderer 的工作主要就是将各种副作用(flags 表示)commit 到宿主环境的 UI 中。整个阶段可以分为三个阶段,分别是 BeforeMutation 阶段、Mutation 阶段和 Layout 阶段

  1. before mutation 阶段(执行DOM操作前):一些准备工作,如处理 DOM 节点渲染/删除后的 autoFocusblur 逻辑、触发getSnapshotBeforeUpdate生命周期方法、调度useEffect
  2. mutation 阶段(执行DOM操作):React 根据调和阶段的计算结果执行 DOM 的增删改操作。
  3. layout 阶段(执行DOM操作后):执行一些可能需要最终的 DOM 结构信息才能完成的工作,比如测量 DOM 元素的尺寸和位置。

注意:在before mutation阶段之前和layout阶段之后还有一些额外工作,涉及到比如useEffect的触发、优先级相关的重置、ref的绑定/解绑。

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

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

相关文章

CUDA-MODE 第一课课后实战(下)

我的课程笔记&#xff0c;欢迎关注&#xff1a;https://github.com/BBuf/how-to-optim-algorithm-in-cuda/tree/master/cuda-mode CUDA-MODE 第一课课后实战&#xff08;下&#xff09; Nsight Compute Profile结果分析 继续对Nsight Compute的Profile结果进行分析&#xff0…

PyQT 串口改动每次点开时更新串口信息

class MainWindow(QWidget, Ui_Form):def __init__(self):super().__init__(parentNone)self.setupUi(self)self.comboBox.installEventFilter(self) # 加载事件过滤器self.comboBox.addItems(get_ports())def eventFilter(self, obj, event): # 定义事件过滤器if isinstance(o…

前端容器化部署:解决重启容器时的静态资源丢失问题

文章目录 什么是前端容器化&#xff1f;重启容器时静态资源丢失的问题解决静态资源丢失的方案1. 使用持久化卷创建和挂载卷使用Docker Compose定义卷 2. 使用对象存储将静态资源上传到对象存储 3. 使用构建时持久化使用CI/CD管道 4. 使用动态加载和缓存使用浏览器缓存使用服务端…

Java 8日期时间API革新:从Date到LocalDate、LocalTime与LocalDateTime的转型与优势解析

文章目录 前言一、基础介绍1.Date2.LocalDate3.LocalTime4.LocalDateTime 二、区别三、推荐场景四、推荐原因总结 前言 在Java的发展历程中&#xff0c;日期和时间的处理一直是开发者们关注的焦点。从早期的java.util.Date类到java.util.Calendar接口&#xff0c;虽然为日期时间…

Linux从0到1——进程池

Linux从0到1——进程池 1. 进程池的概念2. 进程池实现思路3. 进程池的代码实现3.1 创建管道&#xff0c;创建子进程3.2 封装任务3.3 Work接口3.4 发送任务3.5 回收资源&#xff0c;关闭管道&#xff08;重点&#xff09;3.6 改造CreatChannels接口 4. 完整代码 1. 进程池的概念…

数据结构面试-核心概念-问题理解

目录 1.数据结构及其分类 2.时间复杂度与空间复杂度及大O表示法 3.循环队列及其判断队空和队满的方法 4.栈与队列在计算机系统中的应用 5.串的模式匹配算法 6.线索二叉树、二叉搜索树、平衡二叉树 7.哈夫曼树与哈夫曼编码 8.DFS与BFS 9.最小生成树及其构建算法 10.最短…

谭晓生解读:AI如何重塑网络安全的未来?

导语 | 当前&#xff0c;对网络安全而言&#xff0c;每一次新的信息技术浪潮都蕴含着巨大机会&#xff0c;同时也意味着巨大的挑战。大模型的发展&#xff0c;是带来了更大的AI安全风险&#xff0c;还是将赋能提升网络安全呢&#xff1f;网络安全正在迎来一场重大范式转移&…

【网络编程】TCP通信基础模型实现

tcpSer.c #include <myhead.h> #define SER_IP "192.168.119.143" // 设置IP地址 #define SER_PORT 6666 // 设置端口号 int main(int argc, const char *argv[]) {// 1.创建socketint serfd socket(AF_INET, SOCK_STREAM, 0);// 参数1表示ipv4// 参数2表…

基于redis的zset实现排行榜

文章目录 &#x1f31e; Sun Frame&#xff1a;SpringBoot 的轻量级开发框架&#xff08;个人开源项目推荐&#xff09;&#x1f31f; 亮点功能&#x1f4e6; spring cloud模块概览常用工具 &#x1f517; 更多信息1.sun-club-subject集成redis1.sun-club-domain引入依赖2.sun-…

Linux 内核源码分析---内核 Netlink 套接字

linux 内核一直存在的一个严重问题就是内核态和用户态的交互的问题&#xff0c;对于这个问题内核大佬们一直在研究各种方法&#xff0c;想让内核和用户态交互能够安全高效的进行。如系统调用&#xff0c;proc&#xff0c;sysfs 等内存文件系统&#xff0c;但是这些方式一般都比…

从今年的计算机视觉比赛看风向

记第一次参加CV比赛的经历-长三角&#xff08;芜湖&#xff09;人工智能视觉算法大赛-CSDN博客 去年参赛的记录里说了&#xff1a; 最近&#xff0c;同样的由芜湖举办的比赛又上线了&#xff0c;果然&#xff1a; 2023年是这些赛题&#xff0c;典型的CV&#xff1a; 今年变成…

如何高效记录并整理编程学习笔记?一个好的笔记软件往往可以达到事半功倍的学习效果 φ(* ̄0 ̄)

在编程学习的旅程中&#xff0c;良好的笔记习惯不仅是知识积累的基石&#xff0c;更是提升学习效率、巩固学习成果的关键。选择合适的笔记工具&#xff0c;并掌握其高效使用方法&#xff0c;对于每一位编程学习者而言都至关重要。本文将从笔记工具的选择角度出发&#xff0c;探…

Linux 中断机制(一)之中断和异常

目录 一、什么是中断1、概述2、中断的分类 二、中断和异常1、中断和异常2、中断的上下部3、异常4、APIC5、中断描述符表 三、软件实现 一、什么是中断 1、概述 中断&#xff08;interrupt&#xff09;是指在 CPU 正常运行期间&#xff0c; 由外部或内部事件引起的一种机制。 …

Miracast ——随时随地在Wi-Fi®设备上分享高清内容

Miracast 是一种无线显示技术&#xff0c;由 Wi-Fi 联盟开发&#xff0c;允许设备之间通过无线方式分享多媒体内容。 Wi-Fi CERTIFIED Miracast™支持在Miracast设备之间无缝显示多媒体内容。Miracast使用户能够通过无线连接在Wi-Fi设备之间分享多媒体内容&#xff0c;包括高分…

六西格玛绿带培训对企业有什么帮助?

六西格玛&#xff0c;这一源自摩托罗拉、风靡全球的管理哲学和方法论&#xff0c;以其严谨的数据分析、持续改进的流程优化理念&#xff0c;帮助无数企业实现了从“好”到“卓越”的跨越。而六西格玛绿带&#xff0c;作为这一体系中的中坚力量&#xff0c;是连接高层管理者与一…

Linux--C语言之分支结构

文章目录 一、分支结构&#xff08;一&#xff09;概念&#xff08;二&#xff09;条件构建1.关系表达式&#xff1a;2.逻辑表达式&#xff1a;3.常量/变量&#xff1a;值是否非0&#xff0c;取值&#xff08;0|1&#xff09; &#xff08;三&#xff09;选择结构的形式1.单分支…

QT容器组

目录 容器组 Group BoX&#xff08;组&#xff09; Scroll Area&#xff08;组滑动&#xff09; Tool Box&#xff08;分页显示&#xff09; Tab Widget&#xff08;也是分页显示&#xff09; Stacked widget&#xff08;也是分页&#xff09; Frame&#xff08;就一个框…

无字母数字webshell之命令执行

文章目录 无字母数字的webshell构造技巧php7下简单解决问题php5下解决问题glob开始操作 无字母数字的webshell构造技巧 <?php if(isset($_GET[code])){$code $_GET[code];if(strlen($code)>35){die("Long.");}if(preg_match("/[A-Za-z0-9_$]/",$c…

应对FingerprintJS反爬:Selenium的破解策略与技术详解

目录 引言 FingerprintJS技术概述 技术原理 应用场景 应对策略 高级解决方案 代码实现与案例分析 去除webdriver特征 使用Undetected_chromedriver 案例分析&#xff1a;爬取目标网站数据 结论 引言 在现代互联网环境中&#xff0c;网站反爬技术日益成熟&#xff0…

分布式知识总结(基本概念)

文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 文章收录在网站&#xff1a;http://hardyfish.top/ 基本概念 吞吐量 指系统在单位时间能够处理多少个请求 QPS 每秒…