Skip to content

第7章 Hooks 的实现原理

本章要点

  • Hooks 的数据结构:单向链表与 Fiber 节点的绑定关系
  • useState 的完整实现:mount 阶段与 update 阶段的差异
  • useReducer 的工作机制:与 useState 的同源性
  • useEffect 与 useLayoutEffect 的内部调度差异
  • useRef 为什么是最简单的 Hook——以及它为什么不触发重渲染
  • useMemo 与 useCallback 的缓存策略与失效机制
  • useContext 的订阅模型与性能陷阱
  • Hook 规则的技术根因:为什么不能条件调用 Hook
  • Dispatcher 切换机制:React 如何在不同阶段使用不同的 Hook 实现

Hooks 是 React 16.8 引入的最重大的范式变革。表面上,它让函数组件拥有了状态和副作用的能力;但从内核的角度看,Hooks 的设计远比 API 表面呈现的要精妙得多。

当你写下 const [count, setCount] = useState(0) 时,你可能会好奇:一个看似无状态的函数,每次调用都从头执行,它是怎么"记住"上一次的值的?更关键的是,当同一个组件中有多个 useState,React 是怎么知道哪个 state 对应哪个调用的?

答案藏在两个核心设计中:Fiber 节点上的 Hook 链表基于调用顺序的索引机制

7.1 Hook 的数据结构

每个 Hook 在 React 内部对应一个 Hook 对象,这些对象通过 next 指针串成一条单向链表,挂载在 Fiber 节点的 memoizedState 属性上:

typescript
// Hook 的核心数据结构
type Hook = {
  memoizedState: any;        // 当前状态值(不同 Hook 存储不同的东西)
  baseState: any;            // 基准状态(用于并发模式下的状态计算)
  baseQueue: Update<any> | null;  // 上次未处理完的更新队列
  queue: UpdateQueue<any> | null; // 当前待处理的更新队列
  next: Hook | null;         // 指向下一个 Hook
};

// 不同 Hook 的 memoizedState 存储的内容
// useState     → state 值
// useReducer   → state 值
// useEffect    → Effect 对象
// useRef       → { current: value }
// useMemo      → [cachedValue, deps]
// useCallback  → [callback, deps]
// useContext   → context 的当前值(但实际上不使用 Hook 链表)

当 React 渲染一个函数组件时,Hook 链表的构建过程如下:

tsx
function MyComponent() {
  const [name, setName] = useState('React');     // Hook 1
  const [count, setCount] = useState(0);          // Hook 2
  const ref = useRef(null);                        // Hook 3
  useEffect(() => { /* ... */ }, [count]);         // Hook 4
  const memoized = useMemo(() => count * 2, [count]); // Hook 5

  return <div>{name}: {count} (x2 = {memoized})</div>;
}

图 7-1:Hook 链表结构示意图

7.2 Dispatcher:Hook 的两张面孔

React 中的每个 Hook API(如 useState)并不是一个固定的实现,而是一个"门面",它的实际行为取决于当前的 Dispatcher。React 维护了一个全局变量 ReactCurrentDispatcher,在不同的执行阶段会切换到不同的 Dispatcher:

typescript
// React 的 Dispatcher 机制
const ReactCurrentDispatcher = {
  current: null as Dispatcher | null,
};

// 三种主要的 Dispatcher
const HooksDispatcherOnMount: Dispatcher = {
  useState: mountState,
  useEffect: mountEffect,
  useRef: mountRef,
  useMemo: mountMemo,
  useCallback: mountCallback,
  useReducer: mountReducer,
  useContext: readContext,
  // ...
};

const HooksDispatcherOnUpdate: Dispatcher = {
  useState: updateState,
  useEffect: updateEffect,
  useRef: updateRef,
  useMemo: updateMemo,
  useCallback: updateCallback,
  useReducer: updateReducer,
  useContext: readContext,
  // ...
};

const InvalidHooksDispatcher: Dispatcher = {
  useState: throwInvalidHookError,
  useEffect: throwInvalidHookError,
  // ... 所有方法都抛错
};

当 React 开始渲染一个函数组件时,会根据情况设置 Dispatcher:

typescript
function renderWithHooks(
  current: Fiber | null,
  workInProgress: Fiber,
  Component: Function,
  props: any
) {
  // 根据是首次渲染还是更新,设置不同的 Dispatcher
  if (current !== null && current.memoizedState !== null) {
    // 更新阶段
    ReactCurrentDispatcher.current = HooksDispatcherOnUpdate;
  } else {
    // 首次挂载
    ReactCurrentDispatcher.current = HooksDispatcherOnMount;
  }

  // 执行函数组件
  let children = Component(props);

  // 渲染完成后,将 Dispatcher 设为无效版本
  // 这就是为什么在组件外调用 Hook 会报错
  ReactCurrentDispatcher.current = InvalidHooksDispatcher;

  return children;
}

这就是为什么在组件渲染函数外部调用 useState 会得到一个"Invalid hook call"错误——此时的 Dispatcher 已经被切换为 InvalidHooksDispatcher,所有 Hook 调用都会抛出异常。

7.3 useState 的完整实现

useState 是使用最广泛的 Hook,它的实现也是理解所有 Hook 工作原理的基石。

7.3.1 Mount 阶段:初始化

首次渲染时,useState 调用的是 mountState

typescript
function mountState<S>(initialState: (() => S) | S): [S, Dispatch<SetStateAction<S>>] {
  // 1. 创建一个新的 Hook 对象并添加到链表末尾
  const hook = mountWorkInProgressHook();

  // 2. 处理初始值(支持函数形式的惰性初始化)
  if (typeof initialState === 'function') {
    initialState = (initialState as () => S)();
  }

  // 3. 设置初始状态
  hook.memoizedState = initialState;
  hook.baseState = initialState;

  // 4. 创建更新队列
  const queue: UpdateQueue<S> = {
    pending: null,
    lanes: NoLanes,
    dispatch: null,
    lastRenderedReducer: basicStateReducer,
    lastRenderedState: initialState,
  };
  hook.queue = queue;

  // 5. 创建 dispatch 函数(即 setState)
  const dispatch = (queue.dispatch = dispatchSetState.bind(
    null,
    currentlyRenderingFiber,
    queue
  ));

  return [hook.memoizedState, dispatch];
}

// mountWorkInProgressHook 负责创建 Hook 对象并链接到链表
function mountWorkInProgressHook(): Hook {
  const hook: Hook = {
    memoizedState: null,
    baseState: null,
    baseQueue: null,
    queue: null,
    next: null,
  };

  if (workInProgressHook === null) {
    // 这是链表的第一个 Hook
    currentlyRenderingFiber.memoizedState = hook;
    workInProgressHook = hook;
  } else {
    // 追加到链表末尾
    workInProgressHook.next = hook;
    workInProgressHook = hook;
  }

  return hook;
}

基于 VitePress 构建