但行好事 莫问前程
前言
出于以下动机:
- 简化组件:用函数替代类,避免
this
和生命周期混乱。 - 逻辑复用:通过自定义 Hook 抽离重复逻辑(如数据请求、监听事件)。
- 功能聚焦:每个 Hook 专注一个功能(如
useState
管状态,useEffect
管副作用)。
React 16.8 新增特性 —— Hook,让函数式组件也能使用 状态state 和 其他 react 特性。
本文代码基于v18.3.1,内容精确到每一个变量,每一行代码,一起从源码层面学习 hooks 的工作机制。
简介
在学习源码之前,你应该知道以下内容:
- React 使用 双缓冲机制(Double Buffering) 实现 Fiber 树的更新
- Hooks 以 链表形式 存储在 Fiber 的
memoizedState
属性中,每个 Hook 对应链表的一个节点
type Hook = {
memoizedState: any, // 当前状态(如 useState 的值、useEffect 的依赖、useRef的ref对象...)
baseState: any, // 基础状态(用于更新计算)
baseQueue: Update<any, any> | null, // 待处理的更新队列
queue: any,// 待更新队列(如 setState 的批量更新)、用于更新的dispatch函数、更新优先级Lanes等信息
next: Hook | null, // 指向下一个 Hook 节点,用于构成链表
};
type fiber = {
memoizedState: Hook | null, // Hooks 链表的头节点
// ...其他 Fiber 字段(如 stateNode、alternate)
}
然后认识一些重要的变量:
current
: 指向 当前已渲染的 Fiber 树(旧,对应屏幕上显示的内容)。workInProgress
: 指向 正在构建的新 Fiber 树(新,内存中构建的更新版本)。currentHook
: 已渲染的 Fiber 树 中,正在处理的 Hook 节点。workInProgressHook
: 新的 Fiber 树 中,正在构建或更新的 Hook 节点。currentlyRenderingFiber
: 当前正在执行的函数组件对应的 Fiber 节点(即workInProgress
Fiber)。ReactCurrentDispatcher
: 调度器对象,根据当前渲染阶段(Mount or Update)分配不同的调度器。
函数组件的更新和渲染,会触发 updateFunctionComponent
函数,调用 renderWithHooks
函数 -> 执行 Component(props)
组件函数。
我们以 renderWithHooks
为入口,分析 Hook 是如何运行的。
renderWithHooks
react/packages/react-reconciler/src/ReactFiberHooks.new.js
抛开_DEV_
相关的调试代码和一些注释,精简 renderWithHooks
函数:
export function renderWithHooks<Props, SecondArg>(
current: Fiber | null,
workInProgress: Fiber,
Component: (p: Props, arg: SecondArg) => any,
props: Props,
secondArg: SecondArg,
nextRenderLanes: Lanes
): any {
renderLanes = nextRenderLanes;
// 1. 设置当前正在渲染的 Fiber 节点
currentlyRenderingFiber = workInProgress;
// 2. 重置 Hooks 链表指针(初次渲染或更新)
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
// 3. 区分 Mount 和 Update 阶段,选择 Hooks 实现
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
// 4. 执行组件函数(内部会按顺序调用 Hooks)
let children = Component(props, secondArg);
// 5. 重置全局变量,准备下一轮渲染
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
renderLanes = NoLanes;
currentlyRenderingFiber = (null: any);
currentHook = null;
workInProgressHook = null;
return children;
}
解读
分块解读上述代码:
- 设置当前正在渲染的 Fiber 节点
renderLanes = nextRenderLanes;
currentlyRenderingFiber = workInProgress;
renderLanes = nextRenderLanes;
设置渲染的优先级,在 React16 的早期版本中,官方采用 expirationTime
模型来调度渲染的优先级,在 React17+ 等版本中被 Lane
模型所替代。
expirationTime
模型仅通过时间设置优先级,存在灵活性差、优先级冲突(如用户的输入可能无法得到优先处理)、优先级单一等问题。
currentlyRenderingFiber = workInProgress;
如前文所说,通过 currentlyRenderingFiber
变量,可以正确从全局获取到 当前正在执行的函数组件对应的 Fiber 节点(即 workInProgress
Fiber)。
- 重置 workInProgress fiber 状态
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes;
置空 workInProgress
的 Hooks链表、更新队列、渲染优先级,确保每次渲染都是纯净的。
后续 React 会根据函数组件的渲染阶段:
- 搭建一个新 hooks 链表
- 或者 复用
current.memoizedState
即旧fiber Hooks 链表
赋值给 currentlyRenderingFiber.memoizedState
即 workInProgressHook
。
- 区分 Mount 和 Update 阶段,选择 Hooks 实现方式
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
根据 current
和 memoizedState
,判断函数组件状态:
- 首次渲染Mount,
ReactCurrentDispatcher.current = HooksDispatcherOnMount
- 渲染更新Update,
ReactCurrentDispatcher.current = HooksDispatcherOnUpdate
设置派发器 ReactCurrentDispatcher
,确定如何执行组件中的 hooks。
- 执行组件函数(按顺序调用 Hooks)
let children = Component(props, secondArg);
Component(props, secondArg);
执行函数组件,函数中的 hooks 将被依次执行(useState
、useEffect
...)。
- 重置全局变量,准备下一轮渲染
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
renderLanes = NoLanes;
currentlyRenderingFiber = (null: any);
currentHook = null;
workInProgressHook = null;
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
设置默认的派发器,避免 hooks 在非React函数中被调用。
最后,将相关变量重新置空。
小结
renderWithHooks
作为执行函数组件的核心,主要作用为:
- 设置当前正在渲染的 Fiber 节点
- 重置
workInProgress
fiber 状态 - 区分 Mount 和 Update 阶段,选择 Hooks 实现方式
- 执行组件函数(按顺序调用 Hooks)
- 重置全局变量,准备下一轮渲染
接下来,我们继续探索 Hooks 的实现方式。
ReactCurrentDispatcher
上文说到 renderWithHooks
函数,根据函数组件的渲染状态,设置不同的派发器。
// 函数组件为 首次渲染Mount 阶段
const HooksDispatcherOnMount: Dispatcher = {
readContext,
useCallback: mountCallback,
useContext: readContext,
useEffect: mountEffect,
useImperativeHandle: mountImperativeHandle,
useLayoutEffect: mountLayoutEffect,
useInsertionEffect: mountInsertionEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
useDebugValue: mountDebugValue,
useDeferredValue: mountDeferredValue,
useTransition: mountTransition,
useMutableSource: mountMutableSource,
useSyncExternalStore: mountSyncExternalStore,
useId: mountId,
unstable_isNewReconciler: enableNewReconciler,
};
// 函数组件为 渲染更新Update 阶段
const HooksDispatcherOnUpdate: Dispatcher = {
readContext,
useCallback: updateCallback,
useContext: readContext,
useEffect: updateEffect,
useImperativeHandle: updateImperativeHandle,
useInsertionEffect: updateInsertionEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState,
useDebugValue: updateDebugValue,
useDeferredValue: updateDeferredValue,
useTransition: updateTransition,
useMutableSource: updateMutableSource,
useSyncExternalStore: updateSyncExternalStore,
useId: updateId,
unstable_isNewReconciler: enableNewReconciler,
};
可以看到在不同阶段,Hooks 的实现方式(函数)不同。
例如 useState
:
- Mount 阶段 ——
useState: mountState
- Update 阶段 ——
useState: updateState
下文以 useState
为例,看看 hooks 在不同阶段是如何执行的。
useState
mountState
function mountState(initialState) {
// 1. 挂载并获取最新的 Hooks 链表节点
const hook = mountWorkInProgressHook();
// 2. 对 hook 初始化赋值
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue = {
pending: null, // 待更新队列
lanes: NoLanes, // 更新队列的优先级
dispatch: null, // 负责更新state的方法
lastRenderedReducer: basicStateReducer, // 最后一次渲染的reducer
lastRenderedState: (initialState: any), // 最后一次渲染的state值
};
hook.queue = queue;
// 3. 确定触发函数更新的方法
const dispatch = (queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue))
return [hook.memoizedState, dispatch];
}
- 挂载并获取最新的 Hooks 链表节点
const hook = mountWorkInProgressHook();
处于 Mount阶段 的函数组件,每一次 Hooks 的执行,比如 useState
、useEffect
、useRef
都会调用 mountWorkInProgressHook
函数,创建新 hook 节点并挂载,以获取最新的 Hooks 链表节点。
- 对
hook
初始化赋值
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue = {
pending: null, // 待更新队列
lanes: NoLanes, // 更新队列的优先级
dispatch: null, // 负责更新state的方法
lastRenderedReducer: basicStateReducer, // 最后一次渲染的reducer
lastRenderedState: (initialState: any), // 最后一次渲染的state值
};
hook.queue = queue;
useState(initialState)
如传递函数作为参数,initialState
被视为初始化函数,调用取值。
hook.memoizedState = hook.baseState = initialState;
将 initialState
赋值给 hook
节点的 memoizedState
当前状态 和 baseState
基础状态,作为初始状态。
hook.queue = queue;
新建空的队列,作为 hook
的更新队列。
- 确定触发函数更新的方法
queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber, queue)
dispatchSetState.bind
绑定了 目标fibercurrentlyRenderingFiber
和 更新队列queue
,但没有指定 action
。
通过调用 setState
,触发函数组件的状态更新 dispatchSetState
。
return [hook.memoizedState, dispatch];
返回 初始状态hook.memoizedState
和 更新状态的函数dispatch
即setXXX(nextState)
。
mountWorkInProgressHook
function mountWorkInProgressHook(): Hook {
const hook: Hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
mountWorkInProgressHook
函数:
- 每次调用新建一个
hook
节点,const hook: Hook = { //... }
- 首次执行 Hooks,即 当前正在构建Fiber的Hooks链表 为空,
workInProgressHook === null
workInProgressHook = hook;
- 将
hook
作为链表头节点
- 将
currentlyRenderingFiber.memoizedState = workInProgressHook
- 将
workInProgressHook
作为workInProgress
的链表 - (前文说过
currentlyRenderingFiber
指向最新的Fiber,fiber.memoizedState
属性用于存储 Hooks 链表的头节点)
- 将
- 非首次执行 Hooks
workInProgressHook.next = hook;
- 将
hook
挂载到链表末尾
- 将
workInProgressHook = workInProgressHook.next
- 将
workInProgressHook
指向最新的hook
- 将
- 返回 新Fiber 的 Hooks 链表,
return workInProgressHook;
updateState
function updateState(initialState) {
return updateReducer(basicStateReducer, (initialState: any));
}
function basicStateReducer(state, action) {
return typeof action === 'function' ? action(state) : action;
}
useState
在 Update阶段,本质是调用提前设置好 reducer
的 updateReducer
函数(reducer = basicStateReducer
)。
updateReducer
抛开_DEV_
相关的调试代码和一些注释,精简 updateReducer
函数:
function updateReducer(reducer, initialArg, init) {
// 1. 拷贝 current hook 到 workInProcess hook,并返回最新的 Hooks 链表节点
const hook = updateWorkInProgressHook();
// 2. 读取 hook.queue 中待执行的更新队列 pending,合并到 current.baseQueue 队尾
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const pendingQueue = queue.pending;
const current = currentHook;
let baseQueue = current.baseQueue;
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. 遍历 baseQueue 待更新队列,计算出最新的 state
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 = removeLanes(update.lane, OffscreenLane);
// const shouldSkipUpdate = ...
// if (shouldSkipUpdate) {
// ...
// 标记被跳过的更新
// markSkippedUpdateLanes(updateLane);
// }
// 随着遍历 不断更新 newState
if (update.hasEagerState) {
newState = update.eagerState;
} else {
const action = update.action;
newState = reducer(newState, action); // 得到最新的 state 值
}
update = update.next;
} while (update !== null && update !== first);
// 4. 判断是否存在未完成的更新
if (newBaseQueueLast === null) {
// 仅在全部更新完成时,更新 `newBaseState`
newBaseState = newState;
} else {
// 构建环形链表,供恢复时使用
newBaseQueueLast.next = newBaseQueueFirst;
}
// 当 state 值未变化时,跳过渲染
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
// 5. 更新 workInProgress Hook 中对应的值
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
const dispatch = queue.dispatch;
return [hook.memoizedState, dispatch];
}
Update阶段,useState/useReducer
要做的事就很多了,但我们可以简单理解为:
- 将
currentHook
中对应的 hook 节点,克隆到workInProgressHook
中,即 复用旧 Fiber 的 Hooks 链表 - 读取更新队列
queue
,合并执行所有更新(dispatchAction
) - 计算出最新状态
newState
(期间可能跳过、中断、恢复),更新 hook 的状态
看看代码中的具体实现:
- 拷贝
current hook
到workInProcess hook
,并返回最新的 Hooks 链表节点
const hook = updateWorkInProgressHook();
处于 Update阶段 的函数组件,每一次 Hooks 的执行,比如 useState
、useEffect
、useRef
都会调用 updateWorkInProgressHook
函数,将 currentHook
链表 clone给 WorkInProgressHook
链表,并获取最新的 Hooks 链表节点。
hook(current -> workInProcess)
- 读取
hook.queue
中待执行的更新队列pending
,合并到current.baseQueue
队尾
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const pendingQueue = queue.pending;
const current = currentHook;
let baseQueue = current.baseQueue;
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;
}
pendingQueue = queue.pending;
,读取了待执行的更新队列
pendingFirst = pendingQueue.next;
,提供了 pendingQueue
的头节点
baseQueue.next = pendingFirst;
,将 pendingQueue
合并到 baseQueue
在 React 中,渲染可能会被高优先级任务中断(如用户交互),此时 baseQueue
还未全部执行,所以使用合并链表的方式,而不是直接覆盖队列。
- 遍历
baseQueue
待更新队列,计算出最新的state
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 = removeLanes(update.lane, OffscreenLane);
// const shouldSkipUpdate = ...
// if (shouldSkipUpdate) {
// ...
// 标记被跳过的更新
// markSkippedUpdateLanes(updateLane);
// }
// 随着遍历 不断更新 newState
if (update.hasEagerState) {
newState = update.eagerState;
} else {
const action = update.action;
newState = reducer(newState, action); // 得到最新的 state 值
}
update = update.next;
} while (update !== null && update !== first);
do { ... } while ( ... )
例如代码中多次调用 setCount
setCount(count=> count + 1);
setCount(count=> count + 1);
setCount(count=> count + 1);
每次 setCount
产生的 Update
都会被放入 pendingQueue
从而被合并到 baseQueue
,从而计算出最终的状态 newState
。
newState = reducer(newState, action);
调用reducer -> action
,根据上一次 update
产生的 newState
,并更新状态 newState =
。
update.action
就是 setState(nextState)
中的 nextState
,为一个新值或更新函数。
function basicStateReducer(state, action) {
return typeof action === 'function' ? action(state) : action;
}
调用流程为 dispatch(fiber, queue)(nextState) -> dispatchAction(fiber, queue, action)
if (update.hasEagerState)
在 dispatchAction(dispatchSetState)
中,会对比新旧状态 is(eagerState, currentState)
是否相同,从而决定是否执行更新
- 判断是否存在未完成的更新
if (newBaseQueueLast === null) {
// 仅在全部更新完成时,更新 `newBaseState`
newBaseState = newState;
} else {
// 构建环形链表,供恢复时使用
newBaseQueueLast.next = newBaseQueueFirst;
}
上文说过,渲染可能会被高优先级任务中断(如用户交互)。
newBaseQueueLast !== null
,即更新队列还有任务时,只会更新 当前状态hook.memoizedState
,不更新 基础状态hook.baseState
。
- 更新
workInProgress Hook
中对应的值
// 当 state 值未变化时,跳过渲染
if (!is(newState, hook.memoizedState)) {
markWorkInProgressReceivedUpdate();
}
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
最后更新 workInProgressHook
对应的状态。
queue.lastRenderedState = newState;
记录最后的状态值,方便下次更新时对比。
updateWorkInProgressHook
function updateWorkInProgressHook(): Hook {
// 移动 currentHook 指针
let nextCurrentHook: null | Hook;
if (currentHook === null) {
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else {
nextCurrentHook = currentHook.next;
}
// 移动 workInProgressHook 指针
let nextWorkInProgressHook: null | Hook;
if (workInProgressHook === null) {
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
currentHook = nextCurrentHook;
const newHook: Hook = {
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) {
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else {
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
函数看似较长,但目的只有一个 —— 将旧节点的 hook 拷贝出来,组成 workInProgressHook 链表。
- 每次 clone 一个
current hook
节点,const newHook: Hook = { //... }
- 首次执行 Hooks,即 当前正在构建Fiber的Hooks链表 为空,
workInProgressHook === null
workInProgressHook = newHook;
- 将
newHook
作为链表头节点
- 将
currentlyRenderingFiber.memoizedState = workInProgressHook
- 将
workInProgressHook
作为workInProgress
的链表 - (前文说过
currentlyRenderingFiber
指向最新的Fiber,fiber.memoizedState
属性用于存储 Hooks 链表的头节点)
- 将
- 非首次执行 Hooks
workInProgressHook.next = newHook;
- 将
newHook
挂载到链表末尾
- 将
workInProgressHook = workInProgressHook.next
- 将
workInProgressHook
指向最新的hook
- 将
- 返回 新Fiber 的 Hooks 链表,
return workInProgressHook;
总结
简单回顾流程:
- 渲染入口:
renderWithHooks
设置currentlyRenderingFiber
和ReactCurrentDispatcher
。
- Hooks 调用:
useState
、useEffect
等从ReactCurrentDispatcher
获取当前实现。
- 状态管理:
- Hooks 通过
currentHook
和workInProgressHook
操作新旧 Fiber 的链表。
- Hooks 通过
- 提交结果:
- 更新完成后,
workInProgress
Fiber 成为新的current
Fiber。
- 更新完成后,
Hook:
- 核心结构
- 每个 Hook 以单向链表形式存储,
next
指针连接下一个 Hook。 - 严格依赖调用顺序,确保多次渲染时顺序一致。
fiber.memoizedState
属性保存了 当前fiber树的 Hooks链表的 头节点。
- 每个 Hook 以单向链表形式存储,
- 关键属性
memoizedState
:当前状态(如state
、effect
对象、ref
值)。baseState
:更新计算的基准状态(用于中断恢复)。baseQueue
:未完成的更新队列(持久化存储)。queue
:新触发的更新队列(临时环形链表)。- ...
- 工作机制
- 挂载阶段
- 初始化 Hook 链表,绑定
hook.memoizedState
(如useState(initialValue)
)。 - 创建
queue
和dispatch
函数(如setState
) - 返回初始值和触发更新的函数(如
return [hook.memoizedState, dispatch]
)
- 初始化 Hook 链表,绑定
- 更新阶段
- 合并更新队列,将新触发的
queue
合并到baseQueue
。 - 基于
baseState
遍历baseQueue
,执行reducer
计算出最新的state
。 - 更新
workInProgressHook
链表中的属性值,memoizedState
、baseState
、baseQueue
。
- 合并更新队列,将新触发的
- 并发模式
- 优先级调度,高优先级更新可中断低优先级任务,优先渲染。
- 中断恢复,未完成的更新保留在
baseQueue
,下次渲染继续处理。
- 挂载阶段
最后,我们再看看以下规则:
1. 不要在条件、循环和嵌套函数中使用 Hook?
因为 hook
链表需要保证一致性,updateWorkInProgressHook
会clone 旧fiber的 hook节点,给新fiber 使用,错误的顺序、数量 会导致 识别错乱、链表断裂的问题。
2. 不要在普通的 JavaScript 函数中调用 Hook?
Hook 的执行依赖于当前组件的 Fiber节点。
并且 ReactCurrentDispatcher.current
会被默认设置为 ContextOnlyDispatcher
,使用 Hooks 时会报错。
结语
有同学可能发现了,没有讲 dispatchAction
相关的函数,我准备留到下一篇 《Hooks的应用与实现》 一起,感兴趣的话欢迎关注。
希望本文能对你有所帮助。持续更新前端知识,脚踏实地不水文,真的不关注一下吗~
写作不易,如果有收获希望 点赞、收藏 🌹
才疏学浅,如有问题或建议还望指教~