前置知识: React

Hooks原理

3 minAdvanced2026/6/14

React Hooks底层实现原理

概述

React Hooks 的底层实现基于 Fiber 架构。每个函数组件对应的 Fiber 节点上挂载了一个 Hooks 链表,Hooks 按调用顺序以链表形式串联。理解 Hooks 的底层原理有助于避免常见的使用错误(如条件调用 Hooks),也能帮助开发者编写更高效的自定义 Hooks。

基础概念

Hooks 链表结构

Hooks 在 Fiber 上以链表形式存储,每个 Hook 节点包含当前状态和更新队列:

Fiber.memoizedState → Hook1 → Hook2 → Hook3 → null

每个 Hook 节点:
{
  memoizedState,  // 当前状态值
  baseState,      // 初始状态
  queue,          // 更新队列
  next,           // 指向下一个 Hook
}

为什么 Hooks 有使用规则

  • 只在顶层调用:Hooks 按链表顺序匹配,条件调用会破坏顺序,导致状态错乱
  • 只在函数组件中调用:Hooks 依赖 Fiber 上下文,普通函数中没有 Fiber

快速上手

useState 的实现原理

// 简化的 useState 实现
function useState(initialState) {
  // 获取或创建 Hook 节点
  const hook = mountWorkInProgressHook();

  // 初始化状态
  hook.memoizedState = initialState;

  // 创建更新队列
  hook.queue = {
    pending: null, // 待处理的更新
    dispatch: null, // dispatch 函数
    lastRenderedState: initialState, // 上次渲染的状态
  };

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

  return [hook.memoizedState, dispatch];
}

dispatch 的实现

// dispatch 函数:将更新加入队列并调度渲染
function dispatchSetState(fiber, queue, action) {
  // 创建更新对象
  const update = {
    action, // 新值或更新函数
    lane: requestUpdateLane(), // 优先级
    next: null, // 指向下一个更新(环形链表)
  };

  // 将更新加入队列(环形链表)
  const pending = queue.pending;
  if (pending === null) {
    update.next = update; // 指向自己
  } else {
    update.next = pending.next;
    pending.next = update;
  }
  queue.pending = update;

  // 调度渲染
  scheduleUpdateOnFiber(fiber, lane);
}

详细用法

useEffect 的实现原理

// 简化的 useEffect 实现
function useEffect(create, deps) {
  const hook = mountWorkInProgressHook();

  // 存储副作用信息
  hook.memoizedState = {
    create, // 副作用函数
    deps, // 依赖数组
    destroy: undefined, // 清理函数
  };

  // 标记 Fiber 有 Passive 副作用
  currentlyRenderingFiber.flags |= PassiveEffect;
}

// 更新时的 useEffect
function updateEffect(create, deps) {
  const hook = updateWorkInProgressHook();

  // 比较依赖是否变化
  const prevDeps = hook.memoizedState.deps;
  if (areHookInputsEqual(deps, prevDeps)) {
    // 依赖未变化,跳过
    return;
  }

  // 依赖变化,更新副作用
  hook.memoizedState = { create, deps, destroy: undefined };
  currentlyRenderingFiber.flags |= PassiveEffect;
}

useRef 的实现原理

// useRef 本质上是一个始终返回同一对象的 Hook
function useRef(initialValue) {
  const hook = mountWorkInProgressHook();

  // 创建一个可变对象,跨渲染保持引用
  hook.memoizedState = { current: initialValue };
  return hook.memoizedState;
}

// 更新时直接返回同一对象
function updateRef(initialValue) {
  const hook = updateWorkInProgressHook();
  return hook.memoizedState;
}

// 这就是为什么修改 ref.current 不会触发重渲染
// React 不追踪 ref 的变化,只保持引用不变

useMemo 和 useCallback 的实现

// useMemo:缓存计算结果
function useMemo(nextCreate, deps) {
  const hook = mountWorkInProgressHook();
  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, deps];
  return nextValue;
}

function updateMemo(nextCreate, deps) {
  const hook = updateWorkInProgressHook();
  const [prevValue, prevDeps] = hook.memoizedState;

  if (areHookInputsEqual(deps, prevDeps)) {
    return prevValue; // 依赖未变,返回缓存值
  }

  const nextValue = nextCreate();
  hook.memoizedState = [nextValue, deps];
  return nextValue;
}

// useCallback 本质上是 useMemo 的特例
function useCallback(callback, deps) {
  return useMemo(() => callback, deps);
}

常见场景

理解闭包陷阱

// 闭包陷阱:事件处理器中捕获了旧的状态值
function Counter() {
  const [count, setCount] = useState(0);

  function handleClick() {
    // 这里的 count 是渲染时的快照,不是最新值
    setTimeout(() => {
      console.log(count); // 可能是旧值
    }, 1000);
  }

  // 解决方案一:使用函数式更新
  function handleClickFixed() {
    setTimeout(() => {
      setCount((prev) => prev + 1); // 基于最新状态更新
    }, 1000);
  }

  // 解决方案二:使用 ref 保持最新值
  const countRef = useRef(count);
  countRef.current = count;

  function handleClickWithRef() {
    setTimeout(() => {
      console.log(countRef.current); // 始终是最新值
    }, 1000);
  }
}

理解批量更新

// React 18 自动批量更新:所有状态更新合并为一次渲染
function handleClick() {
  setCount((c) => c + 1); // 不会立即渲染
  setFlag((f) => !f); // 不会立即渲染
  setName('张三'); // 不会立即渲染
  // 三次更新合并为一次渲染
}

// 在 React 17 中,只有 React 事件处理器内才会批量更新
// setTimeout 中的更新不会批量处理
// React 18 中所有场景都自动批量更新

注意事项

  • Hooks 的调用顺序必须稳定,不能在条件语句、循环或嵌套函数中调用
  • useEffect 的清理函数在下次 effect 执行前或组件卸载时调用
  • useState 的函数式更新可以避免闭包陷阱,应优先使用
  • useRef 修改 current 不会触发重渲染,适合存储不参与渲染的可变值
  • useMemo 和 useCallback 应在性能分析后使用,不要过度优化
  • React 18 中所有更新都自动批量处理,不再需要 unstable_batchedUpdates

进阶用法

自定义 Hook 的底层原理

// 自定义 Hook 只是复用 Hooks 逻辑的函数
// 调用自定义 Hook 时,其中的 Hooks 会被添加到当前 Fiber 的链表中
function useWindowSize() {
  const [size, setSize] = useState({
    width: window.innerWidth,
    height: window.innerHeight,
  });

  useEffect(() => {
    function handleResize() {
      setSize({
        width: window.innerWidth,
        height: window.innerHeight,
      });
    }
    window.addEventListener('resize', handleResize);
    return () => window.removeEventListener('resize', handleResize);
  }, []);

  return size;
}

// 使用时,useState 和 useEffect 被添加到调用组件的 Fiber 链表中
function App() {
  const size = useWindowSize(); // Hooks 被合并到 App 的链表
  return (
    <div>
      {size.width} x {size.height}
    </div>
  );
}

useSyncExternalStore 的实现

import { useSyncExternalStore } from 'react';

// 用于订阅外部数据源,确保并发模式下的数据一致性
function useOnlineStatus() {
  return useSyncExternalStore(
    // subscribe:订阅函数
    (callback) => {
      window.addEventListener('online', callback);
      window.addEventListener('offline', callback);
      return () => {
        window.removeEventListener('online', callback);
        window.removeEventListener('offline', callback);
      };
    },
    // getSnapshot:获取当前值
    () => navigator.onLine,
    // getServerSnapshot:服务端渲染时的值
    () => true
  );
}