React原理之React整体渲染流程

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

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

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

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

React 的渲染的两个阶段

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

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

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

js 复制代码
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:并发模式

js 复制代码
// 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的绑定/解绑。

相关推荐
前端爆冲4 分钟前
项目中无用export的检测方案
前端
热爱编程的小曾32 分钟前
sqli-labs靶场 less 8
前端·数据库·less
gongzemin43 分钟前
React 和 Vue3 在事件传递的区别
前端·vue.js·react.js
Apifox1 小时前
如何在 Apifox 中通过 Runner 运行包含云端数据库连接配置的测试场景
前端·后端·ci/cd
树上有只程序猿1 小时前
后端思维之高并发处理方案
前端
庸俗今天不摸鱼2 小时前
【万字总结】前端全方位性能优化指南(十)——自适应优化系统、遗传算法调参、Service Worker智能降级方案
前端·性能优化·webassembly
黄毛火烧雪下2 小时前
React Context API 用于在组件树中共享全局状态
前端·javascript·react.js
Apifox2 小时前
如何在 Apifox 中通过 CLI 运行包含云端数据库连接配置的测试场景
前端·后端·程序员
一张假钞2 小时前
Firefox默认在新标签页打开收藏栏链接
前端·firefox
高达可以过山车不行2 小时前
Firefox账号同步书签不一致(火狐浏览器书签同步不一致)
前端·firefox