如何理解闭包在 React Hooks 如 useState 中的应用原理

作者:袖梨 2026-07-26
闭包是 React Hooks 正常工作的底层前提,useState 依赖闭包捕获上一次的 memoizedState 值,useEffect/useCallback/useMemo 的 deps 机制通过重建闭包来更新捕获值,而 useRef 绕过闭包因 current 是可变引用。

闭包不是 React Hooks 的“副作用”,而是它能工作的底层前提——没有闭包,useState 就根本存不住上一次的值。

为什么 useState 的初始值只在首次渲染生效

每次函数组件执行,都会重新运行整个函数体,useState 会被反复调用。但它不会每次都重置 state,靠的就是闭包捕获了上一次 render 中的 memorizedState 值。

React 在 fiber 节点上为每个 hook 维护一个对象,其中 memoizedState 字段被当前 hook 函数闭包引用。下一次 render 时,虽然函数重新执行,但这个闭包仍指向同一个内存位置(由 React 内部链表定位),所以能读到上次保存的值。

  • 如果没闭包,每次调用 useState 都会走初始化逻辑,state 永远是初始值
  • useState 不接受“key”参数,靠的是调用顺序和链表位置匹配,而闭包保证了每次都能拿到对应位置的 memoizedState
  • 这就是为什么 hooks 必须写在顶层:一旦进 if 或循环,调用顺序可能错乱,闭包绑定的链表节点就对不上了

useEffect 里读到旧 state 的本质是闭包“没更新”

所谓“闭包陷阱”,其实是你期望闭包里的变量随 state 变化而变化,但实际它只在定义时捕获了一次。

比如你在 useEffect 回调里用了 count,又没把它放进 deps 数组,那这个回调函数闭包里捕获的就是首次 render 时的 count 值,之后无论 count 怎么变,它都看不到。

  • deps 数组的作用,就是告诉 React:“如果这里面任何一个值变了,就丢掉旧闭包,重建一个新闭包”
  • [] 表示“永远用首次闭包”,传 [count] 表示“count 变了就换闭包”,传 undefinednull 表示“每次都换”
  • 不加 count 到 deps,不是 React “忘了更新”,而是你主动让它一直用老闭包

useCallback/useMemo 的 deps 也是同一套闭包机制

useCallback 返回的函数本身就是一个闭包;useMemo 缓存的值,也依赖于其计算函数内部闭包捕获的变量。

它们的 deps 判断逻辑和 useEffect 完全一致:对比前后两次 deps 数组的每一项是否 Object.is 相等。只要有一个不等,就丢弃旧闭包,执行新函数生成新闭包。

  • 漏写 deps 会导致函数或值“卡在旧状态”,不是 bug,是闭包按预期工作
  • 过度写 deps(比如把整个对象放进去)会导致频繁重建闭包,失去 memo 化意义
  • useRef 存可变值,是因为 ref.current 不参与闭包捕获——它是个可变指针,读写都不依赖闭包快照

真正容易被忽略的点在于:闭包不是 React “引入”的问题,而是 JavaScript 执行模型的自然结果;React 的所有 hook 机制,都是在顺应并约束这个模型。你以为在“修复闭包”,其实是在学会和它共处。

相关文章

精彩推荐