Appearance
React 更新机制详解
在 React 中,任何能够引起 UI 变化的操作都被视为一次"更新"。从用户交互(如点击按钮)到 API 调用(如 setState
),这些触发源最终都会被转化为一个标准的内部更新对象,并进入调度流程。本章节将详细拆解从更新触发到任务调度的全过程,揭示其如何与调度阶段(Scheduling Phase)紧密相连。
一、更新的触发源
React 的更新可以由多种事件触发,它们共同构成了驱动应用动态变化的基础。这些触发源可以大致分为以下几类:
1. 初始渲染
typescript
// React 19.2.0 中的初始渲染
import { createRoot } from 'react-dom/client';
const root = createRoot(document.getElementById('root')!);
root.render(<App />); // 触发初始更新
2. 状态与 Props 变更
typescript
interface CounterState {
count: number;
}
const [count, setCount] = useState<number>(0);
// 同步更新
setCount(count + 1);
// 函数式更新(推荐)
setCount(prev => prev + 1);
// 批量更新(React 18+)
setTimeout(() => {
setCount(c => c + 1); // 自动批处理
setCount(c => c + 1);
}, 1000);
Props 变化:
typescript
interface ChildProps {
value: number;
onUpdate: (value: number) => void;
}
// 父组件重新渲染导致子组件 props 变化
function Parent() {
const [state, setState] = useState(0);
return (
<ChildComponent
value={state}
onUpdate={setState}
/>
);
}
Context 变化:
typescript
interface ThemeContextType {
theme: 'light' | 'dark';
toggleTheme: () => void;
}
const ThemeContext = createContext<ThemeContextType | null>(null);
// Context Provider 值变化触发消费者更新
function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<'light' | 'dark'>('light');
const toggleTheme = useCallback(() => {
setTheme(prev => prev === 'light' ? 'dark' : 'light');
}, []);
return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
{children}
</ThemeContext.Provider>
);
}
3. React 19 新机制
Actions(异步状态管理)
typescript
interface FormData {
name: string;
email: string;
}
function MyComponent() {
const [isPending, startTransition] = useTransition();
const [error, setError] = useState<string | null>(null);
const handleSubmit = async (formData: FormData) => {
try {
startTransition(async () => {
const result = await submitForm(formData);
if (!result.success) {
setError(result.error);
}
});
} catch (err) {
setError(err instanceof Error ? err.message : 'Unknown error');
}
};
return (
<form action={handleSubmit}>
{isPending && <div>Submitting...</div>}
{error && <div>Error: {error}</div>}
{/* form fields */}
</form>
);
}
use(Promise)(Suspense 集成)
typescript
interface ApiResponse {
data: any[];
status: 'success' | 'error';
}
function DataComponent({ promise }: { promise: Promise<ApiResponse> }) {
try {
const result = use(promise); // Promise resolve 时触发更新
if (result.status === 'error') {
throw new Error('Failed to load data');
}
return (
<div>
{result.data.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
} catch (error) {
// 错误边界会捕获这个错误
throw error;
}
}
Server Components(服务端渲染优化)
typescript
// 服务端组件(在服务器上运行)
async function ServerComponent() {
try {
const data = await fetchData();
return <ClientComponent data={data} />;
} catch (error) {
console.error('Server component error:', error);
return <ErrorFallback />;
}
}
// 客户端组件
'use client';
function ClientComponent({ data }: { data: any[] }) {
const [localState, setLocalState] = useState(data);
useEffect(() => {
// 客户端特定逻辑
setLocalState(data);
}, [data]);
return (
<div>
{localState.map(item => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
4. 强制同步更新
typescript
// flushSync:强制同步执行更新
import { flushSync } from 'react-dom';
function handleClick() {
flushSync(() => {
setCount(c => c + 1);
});
// 此时 DOM 已经同步更新
console.log(document.getElementById('counter')?.textContent);
}
尽管触发源多种多样,但它们最终都会殊途同归,进入 React 内部统一的更新处理流程。
二、更新的创建与入队
无论更新源自何处,React 都会执行标准化的内部流程:创建 Update
对象 -> 计算优先级(Lane)-> 将 Update
入队。
1. 创建 Update 对象
typescript
// React 19.2.0 中的 Update 类型定义
interface Update<State> {
eventTime: number;
lane: Lane;
tag: UpdateTag;
payload: any;
callback: (() => void) | null;
next: Update<State> | null;
}
enum UpdateTag {
UpdateState = 0,
ReplaceState = 1,
ForceUpdate = 2,
CaptureUpdate = 3,
}
function createUpdate<State>(eventTime: number, lane: Lane): Update<State> {
const update: Update<State> = {
eventTime,
lane,
tag: UpdateTag.UpdateState,
payload: null,
callback: null,
next: null,
};
if (__DEV__) {
// 开发环境下的调试信息
(update as any)._debugSource = new Error().stack;
}
return update;
}
2. Lane 的计算(优先级系统)
typescript
// Lane 类型定义
type Lane = number;
type Lanes = number;
// 优先级常量
const SyncLane: Lane = 0b0000000000000000000000000000001;
const InputContinuousLane: Lane = 0b0000000000000000000000000000100;
const DefaultLane: Lane = 0b0000000000000000000000000010000;
const TransitionLane1: Lane = 0b0000000000000000000000001000000;
function requestUpdateLane(fiber: Fiber): Lane {
const mode = fiber.mode;
// 非并发模式直接返回同步 Lane
if ((mode & ConcurrentMode) === NoMode) {
return SyncLane;
}
// 检查是否在 Transition 中
if (isTransition()) {
const transition = getCurrentTransition();
if (transition !== null) {
return claimNextTransitionLane();
}
}
// 根据当前事件优先级计算 Lane
const eventPriority = getCurrentEventPriority();
switch (eventPriority) {
case DiscreteEventPriority:
return SyncLane;
case ContinuousEventPriority:
return InputContinuousLane;
case DefaultEventPriority:
default:
return DefaultLane;
}
}
// 性能监控:Lane 使用统计
if (__DEV__) {
let laneUsageStats: Record<string, number> = {};
function trackLaneUsage(lane: Lane) {
const laneName = getLaneName(lane);
laneUsageStats[laneName] = (laneUsageStats[laneName] || 0) + 1;
}
function getLaneName(lane: Lane): string {
if (lane === SyncLane) return 'SyncLane';
if (lane === InputContinuousLane) return 'InputContinuousLane';
if (lane === DefaultLane) return 'DefaultLane';
return `TransitionLane_${lane.toString(2)}`;
}
}
3. 入队 Update(环形链表结构)
typescript
interface UpdateQueue<State> {
baseState: State;
firstBaseUpdate: Update<State> | null;
lastBaseUpdate: Update<State> | null;
shared: SharedQueue<State>;
effects: Array<Update<State>> | null;
}
interface SharedQueue<State> {
pending: Update<State> | null;
interleaved: Update<State> | null;
lanes: Lanes;
}
function enqueueUpdate<State>(
fiber: Fiber,
update: Update<State>,
lane: Lane
): void {
const updateQueue = fiber.updateQueue as UpdateQueue<State>;
if (updateQueue === null) {
// 组件已卸载
if (__DEV__) {
console.warn('Cannot update unmounted component');
}
return;
}
const sharedQueue = updateQueue.shared;
// 标记 Lane
markUpdateLaneFromFiberToRoot(fiber, lane);
// 构建环形链表
const pending = sharedQueue.pending;
if (pending === null) {
// 第一个更新,指向自己形成环
update.next = update;
} else {
// 插入到环形链表中
update.next = pending.next;
pending.next = update;
}
// 更新 pending 指针
sharedQueue.pending = update;
// 性能监控
if (__DEV__) {
trackLaneUsage(lane);
measureUpdateQueueLength(updateQueue);
}
}
// 调试工具:测量更新队列长度
function measureUpdateQueueLength<State>(updateQueue: UpdateQueue<State>): number {
const pending = updateQueue.shared.pending;
if (pending === null) return 0;
let count = 1;
let current = pending.next;
while (current !== pending) {
count++;
current = current!.next;
}
if (__DEV__ && count > 10) {
console.warn(`Large update queue detected: ${count} updates pending`);
}
return count;
}
这个过程确保了所有更新都被规范化,并准备好进入下一步的调度环节。
三、调度入口:连接 Reconciler 与 Scheduler
更新入队后,需要触发调度。这个过程通过两个关键函数实现:
- scheduleUpdateOnFiber - Reconciler 的调度入口
- ensureRootIsScheduled - 连接到 Scheduler 的桥梁
这个流程体现了 React 架构的几个重要特点:
1. 职责分离
- Reconciler 负责 Fiber 树的构建和 Diff 算法
- Scheduler 负责任务的时间切片和优先级调度
- scheduleUpdateOnFiber 和 ensureRootIsScheduled 作为连接层
- Lane 系统 负责优先级管理和批处理
2. 关键桥梁
这两个函数是整个调度系统的关键桥梁:
- 从应用层的状态更新(用户交互、数据变化)
- 到底层的任务调度(时间切片、优先级队列)
- 实现了优雅的解耦和可扩展性
3. 异步实现与性能优化
通过 Scheduler 实现了:
- 时间切片(Time Slicing):将长任务分解为小块
- 优先级调度(Priority Scheduling):高优先级任务优先执行
- 可中断渲染(Interruptible Rendering):支持任务中断和恢复
- 批处理优化(Batching):合并多个更新减少渲染次数
- 并发特性(Concurrent Features):Suspense、Transitions 等
4. React 19 增强特性
- 自动批处理:所有更新默认批处理,包括 setTimeout、Promise 等
- Transition API:区分紧急和非紧急更新
- Suspense 改进:更好的加载状态管理
- Server Components:服务端渲染优化
四、scheduleUpdateOnFiber:调度的统一入口
scheduleUpdateOnFiber
是 React 调度系统的核心入口函数,负责处理 Fiber 节点上的更新调度。以下是移除开发代码后的核心实现和详细解析:
scheduleUpdateOnFiber
函数执行流程
标记更新:调用
markRootUpdated
更新根节点的车道信息渲染阶段检查:
- 如果在渲染阶段收到更新,标记渲染阶段更新车道
- 否则进入正常调度流程
挂起状态处理:
- 检查根节点是否因数据延迟而挂起
- 如果是,调用
prepareFreshStack
中断当前渲染
调度确保:调用
ensureRootIsScheduled
确保根节点被调度同步工作刷新:
- 在特定条件下(同步车道、无执行上下文、非并发模式等)
- 立即刷新同步工作
scheduleUpdateOnFiber 内部函数解析
javascript
export function scheduleUpdateOnFiber(
root: FiberRoot,
fiber: Fiber,
lane: Lane,
) {
// 标记根节点已更新
markRootUpdated(root, lane);
// 检查是否在渲染阶段
if (
(executionContext & RenderContext) !== NoLanes &&
root === workInProgressRoot
) {
// 在渲染阶段收到更新,标记渲染阶段更新车道
workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(
workInProgressRootRenderPhaseUpdatedLanes,
lane,
);
} else {
// 检查根节点是否因延迟而挂起
if (
workInProgressRoot === root &&
workInProgressRootExitStatus === RootSuspendedWithDelay &&
workInProgressSuspendedReason === SuspendedOnData
) {
// 中断当前渲染并切换到新更新
prepareFreshStack(root, NoLanes);
}
// 确保根节点被调度
ensureRootIsScheduled(root);
// 如果满足条件,立即刷新同步工作
if (
lane === SyncLane &&
executionContext === NoContext &&
(fiber.mode & ConcurrentMode) === NoMode &&
!ReactCurrentActQueue.isBatchingLegacy
) {
resetRenderTimer();
flushSyncWorkOnLegacyRootsOnly();
}
}
}
核心调用的重要函数解析
1. markRootUpdated(root, lane)
作用:标记根节点已更新,更新相关车道信息
javascript
export function markRootUpdated(root: FiberRoot, updateLane: Lane) {
// 将更新车道添加到待处理车道
root.pendingLanes |= updateLane;
// 标记可能需要显示加载指示器的车道
if (enableDefaultTransitionIndicator) {
root.indicatorLanes |= updateLane & TransitionLanes;
}
// 清除挂起的车道,因为新更新可能解除阻塞
if (updateLane !== IdleLane) {
root.suspendedLanes = NoLanes;
root.pingedLanes = NoLanes;
root.warmLanes = NoLanes;
}
}
2. prepareFreshStack(root, lanes)
作用:准备新的工作栈,重置渲染状态
javascript
function prepareFreshStack(root: FiberRoot, lanes: Lanes): Fiber {
// 取消之前的超时处理
const timeoutHandle = root.timeoutHandle;
if (timeoutHandle !== noTimeout) {
root.timeoutHandle = noTimeout;
cancelTimeout(timeoutHandle);
}
// 取消待处理的提交
const cancelPendingCommit = root.cancelPendingCommit;
if (cancelPendingCommit !== null) {
root.cancelPendingCommit = null;
cancelPendingCommit();
}
// 重置工作进度栈
resetWorkInProgressStack();
workInProgressRoot = root;
// 创建新的工作进度节点
const rootWorkInProgress = createWorkInProgress(root.current, null);
workInProgress = rootWorkInProgress;
// 重置所有工作进度相关状态
workInProgressRootRenderLanes = lanes;
workInProgressSuspendedReason = NotSuspended;
workInProgressThrownValue = null;
workInProgressRootDidSkipSuspendedSiblings = false;
workInProgressRootIsPrerendering = checkIfRootIsPrerendering(root, lanes);
workInProgressRootDidAttachPingListener = false;
workInProgressRootExitStatus = RootInProgress;
workInProgressRootSkippedLanes = NoLanes;
workInProgressRootInterleavedUpdatedLanes = NoLanes;
workInProgressRootRenderPhaseUpdatedLanes = NoLanes;
workInProgressRootPingedLanes = NoLanes;
workInProgressDeferredLane = NoLane;
workInProgressSuspendedRetryLanes = NoLanes;
workInProgressRootConcurrentErrors = null;
workInProgressRootRecoverableErrors = null;
workInProgressRootDidIncludeRecursiveRenderUpdate = false;
// 获取纠缠的渲染车道
entangledRenderLanes = getEntangledLanes(root, lanes);
// 完成并发更新队列
finishQueueingConcurrentUpdates();
return rootWorkInProgress;
}
3. ensureRootIsScheduled(root)
作用:确保根节点被调度(详见之前的解析)
这个函数是调度系统的核心,负责:
- 将根节点添加到调度队列
- 确保有微任务处理根调度
- 处理同步工作刷新
4. flushSyncWorkOnLegacyRootsOnly()
作用:仅在旧版根节点上刷新同步工作
javascript
function flushSyncWorkOnLegacyRootsOnly() {
// 只处理旧版模式的根节点
if (rootsWithPendingDiscreteUpdates !== null) {
const roots = rootsWithPendingDiscreteUpdates;
rootsWithPendingDiscreteUpdates = null;
roots.forEach(root => {
if ((root.mode & ConcurrentMode) === NoMode) {
flushRoot(root, SyncLane);
}
});
}
flushSyncWorkAcrossRoots_impl(true);
}
五、ensureRootIsScheduled:调度阶段的统一入口
ensureRootIsScheduled
可以被视为 React 调度阶段的 最终统一入口 ,它负责将所有待处理的更新请求,以标准化的方式提交给底层的 Scheduler,从而启动或更新 React 的工作循环。这是 React 实现统一调度和优先级管理的关键所在。
ensureRootIsScheduled函数
javascript
function ensureRootIsScheduled(root: FiberRoot): void {
// 1. 获取当前根节点的调度优先级 (Scheduler Priority)
// 这个优先级是根据 root.pendingLanes 计算得出的,代表了当前根节点上所有待处理更新中最高的优先级。
const currentSchedulerPriority = getHighestPriorityLane(root.pendingLanes)
// 2. 如果当前根节点已经有一个调度任务,并且其优先级低于或等于新的调度优先级,
// 则无需重新调度,直接返回。
// 这避免了重复调度低优先级的任务。
if (
root.callbackNode !== null &&
root.callbackPriority <= currentSchedulerPriority
) {
return
}
// 3. 清除旧的调度任务(如果存在)
// 如果需要重新调度(例如,有更高优先级的更新到来),则取消之前安排的调度任务。
cancelCallback(root.callbackNode)
// 4. 如果没有待处理的更新,则将 callbackNode 和 callbackPriority 重置为 null/NoLane,并返回。
// 这意味着当前根节点没有需要处理的任务。
if (currentSchedulerPriority === NoLane) {
root.callbackNode = null
root.callbackPriority = NoLane
return
}
// 5. 根据 React 优先级计算出对应的调度器优先级 (Scheduler Priority)
// React 内部的 Lane 优先级需要映射到 Scheduler 模块理解的优先级。
const schedulerPriority = reactPriorityToSchedulerPriority(
currentSchedulerPriority,
)
// 6. 计算调度任务的过期时间 (Expiration Time)
// 这决定了任务最迟何时必须执行,即使浏览器不空闲。
const expirationTime = calculateExpirationTime(currentSchedulerPriority)
// 7. 安排一个新的调度任务
// 调用 Scheduler 模块的 `scheduleCallback` 函数,安排一个回调函数。
// 这个回调函数通常是 `performSyncWorkOnRoot` 或 `performConcurrentWorkOnRoot`,
// 它将在浏览器空闲时或过期时间到达时执行,从而启动协调阶段。
root.callbackNode = scheduleCallback(
schedulerPriority,
performWorkOnRoot.bind(null, root),
{ timeout: expirationTime },
)
root.callbackPriority = currentSchedulerPriority
}
// 辅助函数(在实际代码中可能位于其他文件或作为内部实现)
function getHighestPriorityLane(lanes: Lanes): Lane {
/* ... */
}
function cancelCallback(callbackNode: CallbackNode): void {
/* ... */
}
function reactPriorityToSchedulerPriority(
reactPriority: Lane,
): SchedulerPriority {
/* ... */
}
function calculateExpirationTime(priority: Lane): Milliseconds {
/* ... */
}
function scheduleCallback(
priority: SchedulerPriority,
callback: Function,
options: { timeout: Milliseconds },
): CallbackNode {
/* ... */
}
function performWorkOnRoot(
root: FiberRoot,
didTimeout: boolean,
): CallbackNode | null {
/* ... */
}
解析
packages/react-reconciler/src/ReactFiberRootScheduler.js
中的 ensureRootIsScheduled
函数是 React 调度阶段的核心入口之一,它的主要职责是根据当前 Fiber 根节点(FiberRoot
)上待处理的更新(pendingLanes
),决定是否需要向 Scheduler 模块安排一个新的调度任务,以及如何安排这个任务。
获取当前根节点的调度优先级 (
currentSchedulerPriority
):getHighestPriorityLane(root.pendingLanes)
:这个函数会从root.pendingLanes
(一个位掩码,表示所有待处理更新的优先级集合)中,找出优先级最高的那个 Lane。这个 Lane 代表了当前根节点上最紧急的更新。- 目的:确定当前根节点需要处理的最高优先级任务是什么。
避免重复调度:
if (root.callbackNode !== null && root.callbackPriority <= currentSchedulerPriority)
:这里进行了一个重要的优化检查。如果当前根节点已经有一个调度任务(root.callbackNode
不为null
),并且这个已存在的任务的优先级(root.callbackPriority
)已经足够高(即小于或等于currentSchedulerPriority
),那么就没有必要再安排一个新的调度任务了。直接返回,避免不必要的 Scheduler 调用。- 目的:防止重复调度,提高效率。
清除旧的调度任务:
cancelCallback(root.callbackNode)
:如果上述条件不满足(即没有调度任务,或者新来的更新优先级更高),那么就需要重新安排调度。在安排新任务之前,必须取消掉之前可能存在的旧的调度任务。这是因为新的任务可能具有更高的优先级,或者旧的任务已经不再需要。- 目的:确保只有一个有效的调度任务在 Scheduler 中排队,并且总是最高优先级的任务。
处理无更新情况:
if (currentSchedulerPriority === NoLane)
:如果getHighestPriorityLane
返回NoLane
,表示当前根节点上没有任何待处理的更新。在这种情况下,将root.callbackNode
和root.callbackPriority
重置为null
和NoLane
,并直接返回。- 目的:当没有更新时,不进行任何调度。
优先级转换:
const schedulerPriority = reactPriorityToSchedulerPriority(currentSchedulerPriority)
:React 内部使用 Lane 模型来管理优先级,而 Scheduler 模块有自己的一套优先级系统(例如ImmediatePriority
,UserBlockingPriority
,NormalPriority
,LowPriority
,IdlePriority
)。这个函数负责将 React 的 Lane 优先级映射到 Scheduler 对应的优先级。- 目的:将 React 内部的优先级概念转换为 Scheduler 可以理解和处理的优先级。
计算过期时间 (
expirationTime
):const expirationTime = calculateExpirationTime(currentSchedulerPriority)
:对于某些优先级(特别是同步或用户阻塞的优先级),React 会计算一个“过期时间”。这意味着即使浏览器主线程一直很忙,Scheduler 也必须在这个时间点之前强制执行这个任务。这保证了高优先级任务的响应性。- 目的:为调度任务设置一个截止时间,确保高优先级任务不会无限期延迟。
安排新的调度任务:
root.callbackNode = scheduleCallback(...)
:这是与 Scheduler 模块交互的关键步骤。scheduleCallback
函数会向 Scheduler 注册一个回调函数(通常是performWorkOnRoot
),并传入计算出的schedulerPriority
和expirationTime
。performWorkOnRoot.bind(null, root)
:这个回调函数是真正启动 React 协调阶段的入口。当 Scheduler 认为时机合适时(例如,浏览器有空闲时间,或者任务过期),它会调用这个函数,并传入root
对象。{ timeout: expirationTime }
:将过期时间传递给 Scheduler,以便它可以在必要时强制执行任务。
root.callbackPriority = currentSchedulerPriority
:更新root.callbackPriority
,记录当前安排的调度任务的优先级。- 目的:将 React 的更新任务提交给底层的调度器,由调度器在合适的时机执行,从而启动 Render Phase。
scheduleUpdateOnFiber
通过 ensureRootIsScheduled
将工作单元“注册”到调度系统中,然后调度器在认为合适的时机“唤醒”这个工作单元,从而启动渲染的准备和执行流程。而这马上到第二个阶段。
全流程
为了更直观地理解从更新触发到任务调度的完整链路,我们可以通过以下流程图来展示:
从流程图中我们可以看到:
- 职责分离:
Reconciler
负责 "什么需要更新"(创建Update
、管理Fiber
),而Scheduler
负责 "何时执行更新"(管理任务队列、与浏览器协作)。 - 关键桥梁:
ensureRootIsScheduled
和unstable_scheduleCallback
是连接这两个模块的核心接口。 - 异步实现:通过
MessageChannel
将调度任务推入宏任务队列,Scheduler
实现了不阻塞主线程的异步执行,这是 React 并发模式的基石。
