React 核心 Hooks useReducer 实现原理和 Fiber 更新调用链路
React Hooks 中涉及到组件状态维护的 Hooks 包括 useState、useReducer。useState 是 useReducer 的简单语法糖。以下是分析 useReducer 的实现原理,以及 dispatch 后 Fiber 更新调用的链路分析:
React useState 和 useReducer 的实现原理
React 中 useState
和 useReducer
的实现共享底层逻辑,useState
本质上是 useReducer
的一个简化版本。两者都通过 Fiber 架构的更新队列 和 优先级调度机制 实现状态更新控制。以下是源码层面的详细分析:
1. useState
是 useReducer
的语法糖
在 React 源码中,useState
直接调用了 useReducer
,并传入一个预定义的 basicStateReducer
:
// ReactHooks.js
import { useReducer } from './ReactFiberHooks';
function useState(initialState) {
return useReducer(basicStateReducer, initialState);
}
function basicStateReducer(state, action) {
return typeof action === 'function'? action(state) : action;
}
basicStateReducer
的作用:- 如果
action
是函数(如setCount(c => c + 1)
),则调用该函数并传入当前状态,返回新状态。 - 如果
action
是直接值(如setCount(5)
),则直接返回该值作为新状态。
- 如果
2. useReducer
的通用性
useReducer
的实现逻辑主要在 ReactFiberHooks.js
文件中。以下是简化后的核心代码:
// ReactFiberHooks.js
let currentlyRenderingFiber = null; // 当前正在渲染的 Fiber 节点
let workInProgressHook = null; // 当前正在处理的 Hook
function useReducer(reducer, initialState) {
const hook = updateWorkInProgressHook();
if (hook.memoizedState === null) {
// 初始化状态
if (typeof initialState === "function") {
hook.memoizedState = initialState();
} else {
hook.memoizedState = initialState;
}
hook.baseState = hook.memoizedState;
hook.queue = {
pending: null,
interleaved: null,
lanes: NoLanes,
};
}
const dispatch = (action) => {
const update = {
action,
next: null,
};
// 将更新对象添加到更新队列中
const queue = hook.queue;
if (queue.pending === null) {
update.next = update;
} else {
update.next = queue.pending.next;
queue.pending.next = update;
}
queue.pending = update;
// 触发调度更新
scheduleUpdateOnFiber(currentlyRenderingFiber);
};
// 处理更新队列
const { baseState, queue } = hook;
let firstUpdate = queue.pending;
if (firstUpdate !== null) {
let newState = baseState;
let update = firstUpdate;
do {
const action = update.action;
newState = reducer(newState, action);
update = update.next;
} while (update !== firstUpdate);
hook.memoizedState = newState;
hook.baseState = newState;
queue.pending = null;
}
return [hook.memoizedState, dispatch];
}
function updateWorkInProgressHook() {
let nextCurrentHook;
if (workInProgressHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = workInProgressHook.next;
}
let newHook;
if (nextCurrentHook === null) {
// 新的 Hook
newHook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
} else {
// 复用现有的 Hook
newHook = {
memoizedState: nextCurrentHook.memoizedState,
baseState: nextCurrentHook.baseState,
baseQueue: nextCurrentHook.baseQueue,
queue: nextCurrentHook.queue,
next: null,
};
}
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
return newHook;
}
初始化阶段
updateWorkInProgressHook
函数用于获取当前正在处理的 Hook 对象。如果是首次渲染,会创建一个新的 Hook 对象,并将其添加到当前 Fiber 节点的memoizedState
链表中。- 在
useReducer
函数中,如果hook.memoizedState
为null
,表示是首次渲染,会根据initialState
的类型初始化状态,并创建一个空的更新队列。
状态更新阶段
- 当调用
dispatch
函数(即setState
)时,会创建一个更新对象update
,并将其添加到 Hook 的更新队列queue
中。更新队列是一个循环链表,通过pending
指针指向最后一个更新对象。 - 调用
scheduleUpdateOnFiber
函数触发 React 的调度更新流程,通知 React 该组件需要重新渲染。
重新渲染阶段
- 在重新渲染时,会处理 Hook 的更新队列。遍历更新队列中的每个更新对象,调用
reducer
函数计算出新的状态值。 - 更新
hook.memoizedState
和hook.baseState
为新的状态值,并清空更新队列。
useReducer
的返回:
- 当前状态
hook.memoizedState
。 dispatch
函数(即setState
或自定义的dispatch
)。
React useReducer
触发更新的源码调用链路分析
从调用 dispatch(action)
到最终组件更新,React 内部经历了 更新调度、优先级处理、协调(Reconciliation) 和 提交(Commit) 四个核心阶段。以下是基于 React 18 源码的详细调用链路分析:
一、触发更新:dispatch(action)
无论是 useState
还是 useReducer
,状态更新均通过以下流程实现,当调用 dispatch
函数(即 useReducer
返回的第二个值)时,React 内部开始处理更新流程:
-
创建更新对象(Update)
// 源码位置:ReactFiberHooks.js function dispatchReducerAction(fiber, queue, action) { const lane = requestUpdateLane(fiber); // 获取更新优先级(Lane) const update = { lane, // 优先级标记(React 18 并发模式核心) action, // 用户传递的 action(如 { type: 'increment' }) hasEagerState: false, eagerState: null, next: null, // 指向下一个更新对象(链表结构) }; enqueueUpdate(fiber, queue, update); // 将 update 加入队列 scheduleUpdateOnFiber(fiber, lane, eventTime); // 调度更新 }
-
将更新加入队列(
enqueueUpdate
)// 源码位置:ReactUpdateQueue.js function enqueueUpdate(fiber, queue, update) { const pending = queue.pending; if (pending === null) { update.next = update; // 环形链表初始化 } else { update.next = pending.next; pending.next = update; } queue.pending = update; // 更新队列尾部指针 }
二、调度更新:scheduleUpdateOnFiber
调度阶段确定更新的优先级并触发渲染流程:
-
标记 Fiber 树的更新优先级
// 源码位置:ReactFiberWorkLoop.js function scheduleUpdateOnFiber(fiber, lane, eventTime) { const root = markUpdateLaneFromFiberToRoot(fiber, lane); // 标记优先级到根节点 if (root === null) return; ensureRootIsScheduled(root, eventTime); // 确保根节点被调度 }
-
请求调度器执行任务
// 源码位置:ReactFiberWorkLoop.js function ensureRootIsScheduled(root, currentTime) { // 根据优先级选择调度方式(同步或并发) if (newCallbackPriority === SyncLanePriority) { // 同步模式:立即执行 scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root)); } else { // 并发模式:请求时间片 scheduleCallback( schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root), ); } }
三、协调阶段(Reconciliation)
在此阶段,React 遍历 Fiber 树,计算新状态并生成虚拟 DOM:
-
进入渲染流程
// 源码位置:ReactFiberWorkLoop.js function performSyncWorkOnRoot(root) { renderRootSync(root, lanes); // 同步渲染根节点 commitRoot(root); // 提交变更 } function performConcurrentWorkOnRoot(root) { renderRootConcurrent(root, lanes); // 并发渲染根节点(可中断) commitRoot(root); // 提交变更 }
-
处理更新队列(
processUpdateQueue
)// 源码位置:ReactUpdateQueue.js function processUpdateQueue(workInProgress, props, instance, renderLanes) { const queue = workInProgress.updateQueue; let update = queue.shared.pending; queue.shared.pending = null; // 清空队列 let newState = queue.baseState; while (update !== null) { const updateLane = update.lane; if (!isSubsetOfLanes(renderLanes, updateLane)) { // 跳过不满足优先级的更新 continue; } // 调用 reducer 计算新状态 const action = update.action; newState = queue.lastRenderedReducer(newState, action); update = update.next; } workInProgress.memoizedState = newState; // 更新 Fiber 节点的状态 }
-
生成新虚拟 DOM
在协调过程中,React 调用组件函数,生成新的子 Fiber 树:// 源码位置:ReactFiberBeginWork.js function updateFunctionComponent(current, workInProgress, Component, nextProps, renderLanes) { const nextChildren = renderWithHooks( current, workInProgress, Component, nextProps, context, renderLanes, ); reconcileChildren(current, workInProgress, nextChildren, renderLanes); }
四、提交阶段(Commit)
将协调阶段计算的变更应用到真实 DOM,并执行副作用:
-
提交 DOM 变更
// 源码位置:ReactFiberCommitWork.js function commitRootImpl(root, renderPriorityLevel) { commitMutationEffects(root, finishedWork); // 更新 DOM commitLayoutEffects(root, finishedWork); // 执行 layout effects(如 useLayoutEffect) schedulePendingPassiveEffects(); // 调度 passive effects(如 useEffect) }
-
执行副作用(Effects)
// 源码位置:ReactFiberCommitWork.js function commitLayoutEffects(finishedWork, root) { while (nextEffect !== null) { const effectTag = nextEffect.effectTag; if (effectTag & Update) { // 执行 useLayoutEffect 的副作用 commitHookEffectListMount(HookLayout | HookHasEffect, nextEffect); } nextEffect = nextEffect.nextEffect; } }
五、关键数据结构与函数
数据结构/函数 | 作用 |
---|---|
Fiber 节点 | 存储组件状态、更新队列、子节点等信息,是协调过程的核心数据结构。 |
Update 对象 | 描述一个状态更新(action + 优先级),通过链表组织成队列。 |
enqueueUpdate | 将更新对象加入队列,维护环形链表结构。 |
scheduleUpdateOnFiber | 触发调度,根据优先级选择同步或并发渲染模式。 |
processUpdateQueue | 遍历更新队列,调用 reducer 计算最新状态。 |
六、总结:useReducer
更新链路
- 触发更新:
dispatch(action)
→ 创建Update
对象 → 加入队列 → 调度更新。 - 调度阶段:标记优先级 → 请求调度器执行任务(同步或并发模式)。
- 协调阶段:遍历 Fiber 树 → 处理更新队列 → 调用
reducer
→ 生成新虚拟 DOM。 - 提交阶段:更新 DOM → 执行副作用(
useLayoutEffect
/useEffect
)。
核心特性:
- 优先级调度(Lane 模型):React 18+ 根据更新紧急程度(如用户输入、数据加载)动态调整执行顺序。
- 批量更新(Batching):同一事件循环内的多个
dispatch
合并为一次渲染。 - 可中断渲染(Concurrent Mode):高优先级任务可中断低优先级任务的渲染过程,提升用户体验。