2025-09-015 高级前端面试准备指南
1、讲讲React的调度器
在传统的 React 渲染中(如 ReactDOM.render),当组件的状态(state)发生变化时,React 会立即开始进行虚拟 DOM 的比对(Reconciliation)和渲染。这个过程是同步且不可中断的。如果组件树很大,计算量会非常繁重,并且会长时间阻塞浏览器的主线程。
浏览器的主线程负责处理用户交互(点击、输入)、计算样式、布局(Layout)、绘制(Paint)等关键任务。长时间的阻塞会导致页面卡顿、掉帧,用户体验变得非常糟糕。
这时候调度器就应运而生了,核心目标有两个:
- 任务可中断:将耗时的渲染任务分解成小的任务单元,允许React在任务单元之间中断工作,让浏览器有机会处理更高优先级的任务。
- 任务优先级:为不同的更新分配不同的优先级。确保高优先级的任务能够被先处理,从而提高更流畅的用户体验。
它的核心概念和工作原理
时间分片 这是调度器实现可中断的核心机制。React 不再一次性完成整个渲染任务,而是将工作分成一个个小的“工作单元”(Unit of Work)。
- 工作原理:React会在浏览器的每一帧(通常是16.6ms)中分配一个固定的时间片(例如5ms)来执行这些工作单元。
- 合作式调度:在执行完一个工作单元后,React会检查当前时间是否已经用尽。
- 时间片未用尽:继续执行下一个工作单元
- 时间片已用尽:React会主动中断当前渲染工作,将控制权交还给浏览器,让它去处理绘制,用户输入等任务。等到下一帧或有空闲时间时,再从中断的地方继续渲染。
优先级:调度器内部定义了一套优先级机制,用来区分哪些任务应该先执行。优先级从高到低大致如下:
- ImmediatePriority: 需要立即执行的任务,如需要同步更新的输入控制。
- UserBlockingPriority: 用户交互触发的任务,如点击、输入等。这是为了保持应用响应迅速。
- NormalPriority: 普通的异步任务,最常见的优先级,如网络请求返回后更新 UI。
- LowPriority: 可以稍后处理的任务,如不太重要的数据拉取。
- IdlePriority: 空闲时才执行的任务,如分析、日志等非关键任务。
当你使用 setState 或 useState 的 dispatch 函数时,默认的更新优先级是 NormalPriority。而使用 并发特性(如 startTransition) 时,你可以明确地将某些更新标记为低优先级(LowPriority)。
import { startTransition } from 'react';
// 高优先级更新:用户输入,立即响应
setInputValue(input);
// 使用 startTransition 将内部的更新标记为低优先级
startTransition(() => {
// 低优先级更新:根据输入筛选列表,可以中断/等待
setSearchQuery(input);
});
- 任务调度与执行流程
- 任务入队: 当发生更新(如 setState)时,React 会根据更新的来源(是用户点击还是普通异步操作)创建一个带有相应优先级的任务,并将其放入调度器的任务队列中。
- 调度循环:
- 调度器会持续地从任务队列中取出最高优先级的任务来执行。
- 它使用一个名为 workLoop 的函数来循环处理这些任务。
- 在 workLoop 中,React 会执行一个工作单元,然后检查是否应该中断(时间片用尽或有更高优先级任务插队)。
- 中断与恢复:
- 如果需要中断,React 会保存当前的工作进度(存储在 Fiber 树上),然后退出。
- 调度器通过浏览器的 requestIdleCallback 的 polyfill 来在浏览器空闲时请求回调,或者使用 setTimeout、MessageChannel 等来模拟,以便在适当的时机恢复执行被中断的任务。
- 任务完成: 当所有工作单元都执行完毕,Reconciliation 阶段结束,React 就会提交(Commit)更新到真实的 DOM。
如何使用?并发特性(Concurrent Features) 作为普通开发者,你并不会直接调用调度器的 API。相反,你通过使用 React 提供的并发特性来间接利用调度器的能力。
- startTransition: 这是最重要的 API。它允许你将一个更新标记为“过渡”(非紧急),使其优先级降低,可以被更紧急的更新(如输入)打断。如果被打断,React 会丢弃未完成的渲染结果,直接处理最新的更新。
- useDeferredValue: 返回一个值的“延迟”版本,它会“滞后”于原始值。React 会在后台先用旧值渲染,然后再用新值进行低优先级的重新渲染。非常适合用于输入防抖和优化渲染性能。
: 虽然主要用于代码分割和数据获取,但 Suspense 与调度器协同工作,可以在组件“正在加载”时先让出主线程,显示降级 UI(fallback),等数据准备好后再以低优先级进行渲染。
总结:
| 特性 | 描述 |
|---|---|
| 目标 | 实现可中断的异步渲染和基于优先级的任务调度,提升用户体验。 |
| 核心机制 | 时间分片(将任务分片,在每帧中固定时间执行)和优先级(区分任务紧急程度)。 |
| 关键角色 | 与 Fiber 架构 深度集成,Fiber 是工作单元,调度器是调度中心。 |
| 开发者接口 | 通过 startTransition、useDeferredValue、Suspense 等并发特性使用。 |
| 底层实现 | 独立的 scheduler npm 包,使用 MessageChannel 等模拟 requestIdleCallback。 |