Appearance
第 9 章第 2 节:并发渲染的工作流程
开启并发模式后,React 的渲染流程发生了根本性的变化。传统的同步渲染是一旦开始就无法停止的“长任务”,而并发渲染则是一个可中断、可恢复的“合作式”过程。这个过程的核心就是并发工作循环(Concurrent Work Loop)。
1. workLoopConcurrent:可中断的循环
并发渲染的主循环位于 ReactFiberWorkLoop.js 的 workLoopConcurrent 函数中。其简化后的逻辑如下:
javascript
// packages/react-reconciler/src/ReactFiberWorkLoop.js
function workLoopConcurrent() {
// 只要有工作要做,并且不需要让出主线程,就持续执行
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}这个 while 循环是并发渲染的心脏。它持续处理工作单元(performUnitOfWork),但在每处理完一个单元后,都会通过 shouldYield() 进行一次“体检”,以决定是继续工作还是“下班休息”。
workInProgress:指向当前正在处理的 Fiber 节点。当整棵 Fiber 树都处理完毕后,它会变为null。performUnitOfWork:执行单个 Fiber 节点的渲染工作,包括调用组件函数(beginWork)和完成该节点(completeWork)。
2. shouldYield:渲染的“刹车”
shouldYield() 函数是实现并发的关键,它决定了 React 何时应该暂停当前的渲染工作,将控制权交还给浏览器。这使得浏览器可以在渲染间隙处理更高优先级的任务,如用户输入、动画等,从而避免页面卡顿。
shouldYield 的判断逻辑主要基于两点:
- 时间分片(Time Slicing):每个渲染任务被分配在一个很短的时间片内(通常是几毫秒)。如果当前工作占用的时间超过了这个阈值,
shouldYield就会返回true。 - 更高优先级的任务:如果在渲染过程中,一个更高优先级的更新(如用户点击触发的更新)被调度,
shouldYield也会返回true,以便 React 可以立即暂停当前低优先级的渲染,转而处理这个紧急任务。
shouldYield 的实现通常会委托给 Scheduler(调度器)包中的 shouldYieldToHost 函数。
javascript
// packages/scheduler/src/forks/Scheduler.js
function shouldYieldToHost() {
// 获取当前时间
const currentTime = getCurrentTime();
// 检查当前任务是否已经超时
if (currentTime >= deadline) {
// 如果当前任务已经超时,说明它已经占用了太长时间。
// 但是,我们还需要检查是否有待处理的更高优先级的事件(如用户输入)。
if (navigator.scheduling.isInputPending()) {
// 如果有用户输入事件,则必须让出主线程,让浏览器优先处理输入。
return true;
}
// 如果没有紧急的用户输入,即使超时了,也可以再多运行一小段时间。
return false;
}
// 如果还没到 deadline,就继续工作。
return false;
}设计思考:shouldYieldToHost 的设计体现了“合作式调度”的思想。它不仅仅是简单地检查时间是否用完,还会通过 navigator.scheduling.isInputPending() API 来“侦测”是否有用户输入事件正在等待处理。这使得 React 的中断决策更加智能:
- 如果用户正在与页面交互,React 会毫不犹豫地暂停渲染,确保交互的流畅性。
- 如果只是后台的渲染任务,即使稍微超时,只要没有紧急事件,React 也会“宽容地”让它继续运行,以尽快完成渲染。
3. 工作流程总结
并发渲染的工作流程可以概括为以下步骤:
- 任务调度:一个更新被调度,Scheduler 将其放入任务队列,并向浏览器请求一个空闲时间片。
- 进入循环:浏览器在空闲时,调用 React 的
workLoopConcurrent。 - 执行工作单元:循环开始,
performUnitOfWork处理一个 Fiber 节点。 - 检查是否中断:
shouldYield被调用,检查时间是否用完或是否有更高优先级的任务。 - 决策:
- 如果
shouldYield返回false:循环继续,处理下一个 Fiber 节点。 - 如果
shouldYield返回true:workLoopConcurrent退出,React 记录下当前的工作进度,并将控制权交还给浏览器。等待浏览器在下一个空闲时间片再次调用它,从上次中断的地方继续工作。
- 如果
- 完成或中断:循环不断重复,直到整棵树渲染完成(
workInProgress变为null),或者被一个更高优先级的更新中断。
通过这种可中断、可恢复的循环机制,React 成功地将一个宏大的渲染任务分解成了无数个可以在一帧内完成的小任务,从而在保证复杂 UI 能够最终被渲染出来的同时,也确保了应用始终对用户保持响应。