首先回顾一下前文Hook 原理(概览), 其主要内容有:
function类型的fiber节点, 它的处理函数是updateFunctionComponent, 其中再通过renderWithHooks调用function.function中, 通过Hook Api(如: useState, useEffect)创建Hook对象.
状态Hook实现了状态持久化(等同于class组件维护fiber.memoizedState).副作用Hook则实现了维护fiber.flags,并提供副作用回调(类似于class组件的生命周期回调)Hook对象构成一个链表结构, 并挂载到fiber.memoizedState之上.fiber树更新阶段, 把current.memoizedState链表上的所有Hook按照顺序克隆到workInProgress.memoizedState上, 实现数据的持久化.在此基础之上, 本节将深入分析状态Hook的特性和实现原理.
在fiber初次构造阶段, useState对应源码mountState, useReducer对应源码mountReducer
mountState:
- function mountState<S>(
-
- initialState: (() => S) | S,
-
- ): [S, Dispatch<BasicStateAction<S>>] {
- // 1. 创建hook
-
- const hook = mountWorkInProgressHook();
-
- if (typeof initialState === 'function') {
-
- initialState = initialState();
-
- }
-
- // 2. 初始化hook的属性
-
- // 2.1 设置 hook.memoizedState/hook.baseState
-
- // 2.2 设置 hook.queue
-
- hook.memoizedState = hook.baseState = initialState;
-
- const queue = (hook.queue = {
-
- pending: null,
-
- dispatch: null,
-
- // queue.lastRenderedReducer是内置函数
-
- lastRenderedReducer: basicStateReducer,
-
- lastRenderedState: (initialState: any),
-
- });
-
- // 2.3 设置 hook.dispatch
-
- const dispatch: Dispatch<
-
- BasicStateAction<S>,
-
- > = (queue.dispatch = (dispatchAction.bind(
-
- null,
-
- currentlyRenderingFiber,
-
- queue,
-
- ): any));
-
- // 3. 返回[当前状态, dispatch函数]
-
- return [hook.memoizedState, dispatch];
-
- }
-
- mountReducer:
-
-
- function mountReducer<S, I, A>(
-
- reducer: (S, A) => S,
-
- initialArg: I,
-
- init?: I => S,
-
- ): [S, Dispatch<A>] {
-
- // 1. 创建hook
-
- const hook = mountWorkInProgressHook();
-
- let initialState;
-
- if (init !== undefined) {
-
- initialState = init(initialArg);
-
- } else {
-
- initialState = ((initialArg: any): S);
-
- }
-
- // 2. 初始化hook的属性
-
- // 2.1 设置 hook.memoizedState/hook.baseState
-
- hook.memoizedState = hook.baseState = initialState;
-
- // 2.2 设置 hook.queue
-
- const queue = (hook.queue = {
-
- pending: null,
-
- dispatch: null,
-
- // queue.lastRenderedReducer是由外传入
-
- lastRenderedReducer: reducer,
-
- lastRenderedState: (initialState: any),
-
- });
-
- // 2.3 设置 hook.dispatch
-
- const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
-
- null,
-
- currentlyRenderingFiber,
-
- queue,
-
- ): any));
-
- // 3. 返回[当前状态, dispatch函数]
-
- return [hook.memoizedState, dispatch];
-
- }
mountState和mountReducer逻辑简单: 主要负责创建hook, 初始化hook的属性, 最后返回[当前状态, dispatch函数].
唯一的不同点是hook.queue.lastRenderedReducer:
mountState使用的是内置的basicStateReducer - function basicStateReducer
(state: S, action: BasicStateAction): S { -
- return typeof action === 'function' ? action(state) : action;
-
- }
mountReducer使用的是外部传入自定义reducer可见mountState是mountReducer的一种特殊情况, 即useState也是useReducer的一种特殊情况, 也是最简单的情况.
useState可以转换成useReducer:
- const [state, dispatch] = useState({ count: 0 });
-
- // 等价于
-
- const [state, dispatch] = useReducer(
-
- function basicStateReducer(state, action) {
-
- return typeof action === 'function' ? action(state) : action;
-
- },
-
- { count: 0 },
-
- );
-
- // 当需要更新state时, 有2种方式
-
- dispatch({ count: 1 }); // 1.直接设置
-
- dispatch(state => ({ count: state.count + 1 })); // 2.通过回调函数设置
userReducer的官网示例:
- const [state, dispatch] = useReducer(
-
- function reducer(state, action) {
-
- switch (action.type) {
-
- case 'increment':
-
- return { count: state.count + 1 };
-
- case 'decrement':
-
- return { count: state.count - 1 };
-
- default:
-
- throw new Error();
-
- }
-
- },
-
- { count: 0 },
-
- );// 当需要更新state时, 只有1种方式
dispatch({ type: 'decrement' });
可见, useState就是对useReducer的基本封装, 内置了一个特殊的reducer(后文不再区分useState, useReducer, 都以useState为例).创建hook之后返回值[hook.memoizedState, dispatch]中的dispatch实际上会调用reducer函数.
在useState(initialState)函数内部, 设置hook.memoizedState = hook.baseState = initialState;, 初始状态被同时保存到了hook.baseState,hook.memoizedState中.
hook.memoizedState: 当前状态hook.baseState: 基础状态, 作为合并hook.baseQueue的初始值(下文介绍).最后返回[hook.memoizedState, dispatch], 所以在function中使用的是hook.memoizedState.
有如下代码:hook-status - CodeSandbox
0
初次渲染时count = 0, 这时hook对象的内存状态如下:

点击button, 通过dispatch函数进行更新, dispatch实际就是dispatchAction:
- function dispatchAction
( -
- fiber: Fiber,
-
- queue: UpdateQueue
, -
- action: A,
-
- ) {
-
- // 1. 创建update对象
-
- const eventTime = requestEventTime();
-
- const lane = requestUpdateLane(fiber); // Legacy模式返回SyncLane
-
- const update: Update
= { -
- lane,
-
- action,
-
- eagerReducer: null,
-
- eagerState: null,
-
- next: (null: any),
-
- };
-
- // 2. 将update对象添加到hook.queue.pending队列
-
- const pending = queue.pending;
-
- if (pending === null) {
-
- // 首个update, 创建一个环形链表
-
- update.next = update;
-
- } else {
-
- update.next = pending.next;
-
- pending.next = update;
-
- }
-
- queue.pending = update;
-
- const alternate = fiber.alternate;
-
- if (
-
- fiber === currentlyRenderingFiber ||
-
- (alternate !== null && alternate === currentlyRenderingFiber)
-
- ) {
-
- // 渲染时更新, 做好全局标记
-
- didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
-
- } else {
-
- // ...省略性能优化部分, 下文介绍
-
- // 3. 发起调度更新, 进入`reconciler 运作流程`中的输入阶段.
-
- scheduleUpdateOnFiber(fiber, lane, eventTime);
-
- }
-
- }
逻辑十分清晰:
update对象, 其中update.lane代表优先级(可回顾fiber 树构造(基础准备)中的update优先级).update对象添加到hook.queue.pending环形链表.
环形链表的特征: 为了方便添加新元素和快速拿到队首元素(都是O(1)), 所以pending指针指向了链表中最后一个元素.scheduleUpdateOnFiber, 进入reconciler 运作流程中的输入阶段.从调用scheduleUpdateOnFiber开始, 进入了react-reconciler包, 其中的所有逻辑可回顾reconciler 运作流程, 本节只讨论状态Hook相关逻辑.
注意: 本示例中虽然同时执行了 3 次 dispatch, 会请求 3 次调度, 由于调度中心的节流优化, 最后只会执行一次渲染
在fiber树构造(对比更新)过程中, 再次调用function, 这时useState对应的函数是updateState
- function updateState
( -
- initialState: (() => S) | S,
-
- ): [S, Dispatch<BasicStateAction
>] { -
- return updateReducer(basicStateReducer, (initialState: any));
-
- }
实际调用updateReducer.
在执行updateReducer之前, hook相关的内存结构如下:

- function updateReducer
( -
- reducer: (S, A) => S,
-
- initialArg: I,
-
- init?: I => S,
-
- ): [S, Dispatch] {
-
- // 1. 获取workInProgressHook对象
-
- const hook = updateWorkInProgressHook();
-
- const queue = hook.queue;
-
- queue.lastRenderedReducer = reducer;
-
- const current: Hook = (currentHook: any);
-
- let baseQueue = current.baseQueue;
-
- // 2. 链表拼接: 将 hook.queue.pending 拼接到 current.baseQueue
-
- const pendingQueue = queue.pending;
-
- if (pendingQueue !== null) {
-
- if (baseQueue !== null) {
-
- const baseFirst = baseQueue.next;
-
- const pendingFirst = pendingQueue.next;
-
- baseQueue.next = pendingFirst;
-
- pendingQueue.next = baseFirst;
-
- }
-
- current.baseQueue = baseQueue = pendingQueue;
-
- queue.pending = null;
-
- }
-
- // 3. 状态计算
-
- if (baseQueue !== null) {
-
- const first = baseQueue.next;
-
- let newState = current.baseState;
-
- let newBaseState = null;
-
- let newBaseQueueFirst = null;
-
- let newBaseQueueLast = null;
-
- let update = first;
-
- do {
-
- const updateLane = update.lane;
-
- // 3.1 优先级提取update
-
- if (!isSubsetOfLanes(renderLanes, updateLane)) {
-
- // 优先级不够: 加入到baseQueue中, 等待下一次render
-
- const clone: Update
= { -
- lane: updateLane,
-
- action: update.action,
-
- eagerReducer: update.eagerReducer,
-
- eagerState: update.eagerState,
-
- next: (null: any),
-
- };
-
- if (newBaseQueueLast === null) {
-
- newBaseQueueFirst = newBaseQueueLast = clone;
-
- newBaseState = newState;
-
- } else {
-
- newBaseQueueLast = newBaseQueueLast.next = clone;
-
- }
-
- currentlyRenderingFiber.lanes = mergeLanes(
-
- currentlyRenderingFiber.lanes,
-
- updateLane,
-
- );
-
- markSkippedUpdateLanes(updateLane);
-
- } else {
-
- // 优先级足够: 状态合并
-
- if (newBaseQueueLast !== null) {
-
- // 更新baseQueue
-
- const clone: Update
= { -
- lane: NoLane,
-
- action: update.action,
-
- eagerReducer: update.eagerReducer,
-
- eagerState: update.eagerState,
-
- next: (null: any),
-
- };
-
- newBaseQueueLast = newBaseQueueLast.next = clone;
-
- }
-
- if (update.eagerReducer === reducer) {
-
- // 性能优化: 如果存在 update.eagerReducer, 直接使用update.eagerState.避免重复调用reducer
-
- newState = ((update.eagerState: any): S);
-
- } else {
-
- const action = update.action;
-
- // 调用reducer获取最新状态
-
- newState = reducer(newState, action);
-
- }
-
- }
-
- update = update.next;
-
- } while (update !== null && update !== first);
-
- // 3.2. 更新属性
-
- if (newBaseQueueLast === null) {
-
- newBaseState = newState;
-
- } else {
-
- newBaseQueueLast.next = (newBaseQueueFirst: any);
-
- }
-
- if (!is(newState, hook.memoizedState)) {
-
- markWorkInProgressReceivedUpdate();
-
- }
-
- // 把计算之后的结果更新到workInProgressHook上
-
- hook.memoizedState = newState;
-
- hook.baseState = newBaseState;
-
- hook.baseQueue = newBaseQueueLast;
-
- queue.lastRenderedState = newState;
-
- }
-
-
- return [hook.memoizedState, dispatch];
-
- }
updateReducer函数, 代码相对较长, 但是逻辑分明:
调用updateWorkInProgressHook获取workInProgressHook对象
链表拼接: 将 hook.queue.pending 拼接到 current.baseQueue

状态计算
update优先级不够: 加入到 baseQueue 中, 等待下一次 render
update优先级足够: 状态合并
更新属性

dispatchAction函数中, 在调用scheduleUpdateOnFiber之前, 针对update对象做了性能优化.
queue.pending中只包含当前update时, 即当前update是queue.pending中的第一个updatequeue.lastRenderedReducer,计算出update之后的 state, 记为eagerStateeagerState与currentState相同, 则直接退出, 不用发起调度更新.queue.pending上的update会在下一次render时再次合并.- function dispatchAction
( -
- fiber: Fiber,
-
- queue: UpdateQueue
, -
- action: A,
-
- ) {
-
- // ...省略无关代码 ...只保留性能优化部分代码:
-
- // 下面这个if判断, 能保证当前创建的update, 是`queue.pending`中第一个`update`. 为什么? 发起更新之后fiber.lanes会被改动(可以回顾`fiber 树构造(对比更新)`章节), 如果`fiber.lanes && alternate.lanes`没有被改动, 自然就是首个update
-
- if (
-
- fiber.lanes === NoLanes &&
-
- (alternate === null || alternate.lanes === NoLanes)
-
- ) {
-
- const lastRenderedReducer = queue.lastRenderedReducer;
-
- if (lastRenderedReducer !== null) {
-
- let prevDispatcher;
-
- const currentState: S = (queue.lastRenderedState: any);
-
- const eagerState = lastRenderedReducer(currentState, action);
-
- // 暂存`eagerReducer`和`eagerState`, 如果在render阶段reducer==update.eagerReducer, 则可以直接使用无需再次计算
-
- update.eagerReducer = lastRenderedReducer;
-
- update.eagerState = eagerState;
-
- if (is(eagerState, currentState)) {
-
- // 快速通道, eagerState与currentState相同, 无需调度更新
-
- // 注: update已经被添加到了queue.pending, 并没有丢弃. 之后需要更新的时候, 此update还是会起作用
-
- return;
-
- }
-
- }
-
- }
-
- // 发起调度更新, 进入`reconciler 运作流程`中的输入阶段.
-
- scheduleUpdateOnFiber(fiber, lane, eventTime);
-
- }
为了验证上述优化, 可以查看这个 demo:hook-throttle - CodeSandbox
上述示例都是为在Legacy模式下, 所以均为同步更新. 所以update对象会被全量合并,hook.baseQueue和hook.baseState并没有起到实质作用.
虽然在v17.x版本中, 并没有Concurrent模式的入口, 即将发布的v18.x版本将全面进入异步时代, 所以本节提前梳理一下update异步合并的逻辑. 同时加深hook.baseQueue和hook.baseState的理解.
假设有一个queue.pending链表, 其中update优先级不同, 绿色表示高优先级, 灰色表示低优先级, 红色表示最高优先级.
在执行updateReducer之前, hook.memoizedState有如下结构(其中update3, update4是低优先级):

链表拼接:
queue.pending拼接到current.baseQueue 
状态计算:
update1, update2这 2 个高优先级的update, 所以最后memoizedState=2update, 等待下一次renderupdate3开始, 随后的所有update都会被添加到baseQueue, 由于update2已经是高优先级, 会设置update2.lane=NoLane将优先级升级到最高(红色表示).baseState代表第一个低优先级update3之前的state, 在本例中, baseState=1
function节点被处理完后, 高优先级的update, 会率先被使用(memoizedState=2). 一段时间后, 低优先级update3, update4符合渲染, 这种情况下再次执行updateReducer重复之前的步骤.
链表拼接:
queue.pending = null, 故拼接前后没有实质变化
状态计算:
update.lane都符合渲染优先级, 所以最后的内存结构与同步更新一致(memoizedState=4,baseState=4).
结论: 尽管
update链表的优先级不同, 中间的render可能有多次, 但最终的更新结果等于update链表按顺序合并.
本节深入分析状态Hook即useState的内部原理, 从同步,异步更新理解了update对象的合并方式, 最终结果存储在hook.memoizedState供给function使用.