CodeWalk

React Hooks 原理:useState 和 useEffect 是如何工作的?什么是闭包陷阱?

作者:苦行僧 · 2026-05-30 12:55

请说明 React useState 和 useEffect 的内部实现原理,以及常见的闭包陷阱问题。

回答

苦行僧

useState 原理

  • 每个 Fiber 节点维护一个 Hooks 链表(memoizedState
  • 首次渲染时,按调用的顺序将每个 Hook 的 state 和 dispatcher 存入链表
  • 更新时,按相同顺序取出对应的 Hook 值
  • setState 会触发当前组件的重新渲染
  • 关键保证:Hook 调用顺序必须稳定,不能放在条件/循环中
// 简化原理
let currentHook = 0;
const hooks = [];

function useState(initial) {
  const hookIndex = currentHook;
  hooks[hookIndex] = hooks[hookIndex] ?? initial;
  
  const setState = (newVal) => {
    hooks[hookIndex] = newVal;
    scheduleUpdate(); // 触发重新渲染
  };
  
  currentHook++;
  return [hooks[hookIndex], setState];
}

useEffect 原理

  • useEffect 的回调和依赖数组也存入 Hook 链表
  • 每次渲染时,对比依赖是否变化(Object.is 比较)
  • 依赖变化则标记副作用,在 commit 阶段后异步执行
  • 清除函数在重新执行前或组件卸载时调用

闭包陷阱

function Counter() {
  const [count, setCount] = useState(0);
  
  useEffect(() => {
    const timer = setInterval(() => {
      setCount(count + 1); // ❌ count 永远是初始值 0
    }, 1000);
    return () => clearInterval(timer);
  }, []); // 依赖为空,闭包捕获了初始 count
  
  // 修复:使用函数式更新
  // setCount(prev => prev + 1);
}

解决方案

  • 函数式更新:setCount(c => c + 1)
  • 正确填写依赖数组
  • useRef 保存可变引用
  • useCallback/useMemo 管理引用相等性