闭包是 React Hooks 正常工作的底层前提,useState 依赖闭包捕获上一次的 memoizedState 值,useEffect/useCallback/useMemo 的 deps 机制通过重建闭包来更新捕获值,而 useRef 绕过闭包因 current 是可变引用。
闭包不是 React Hooks 的“副作用”,而是它能工作的底层前提——没有闭包,useState 就根本存不住上一次的值。
每次函数组件执行,都会重新运行整个函数体,useState 会被反复调用。但它不会每次都重置 state,靠的就是闭包捕获了上一次 render 中的 memorizedState 值。
React 在 fiber 节点上为每个 hook 维护一个对象,其中 memoizedState 字段被当前 hook 函数闭包引用。下一次 render 时,虽然函数重新执行,但这个闭包仍指向同一个内存位置(由 React 内部链表定位),所以能读到上次保存的值。
useState 都会走初始化逻辑,state 永远是初始值useState 不接受“key”参数,靠的是调用顺序和链表位置匹配,而闭包保证了每次都能拿到对应位置的 memoizedState
所谓“闭包陷阱”,其实是你期望闭包里的变量随 state 变化而变化,但实际它只在定义时捕获了一次。
比如你在 useEffect 回调里用了 count,又没把它放进 deps 数组,那这个回调函数闭包里捕获的就是首次 render 时的 count 值,之后无论 count 怎么变,它都看不到。
[] 表示“永远用首次闭包”,传 [count] 表示“count 变了就换闭包”,传 undefined 或 null 表示“每次都换”count 到 deps,不是 React “忘了更新”,而是你主动让它一直用老闭包useCallback 返回的函数本身就是一个闭包;useMemo 缓存的值,也依赖于其计算函数内部闭包捕获的变量。
它们的 deps 判断逻辑和 useEffect 完全一致:对比前后两次 deps 数组的每一项是否 Object.is 相等。只要有一个不等,就丢弃旧闭包,执行新函数生成新闭包。
useRef 存可变值,是因为 ref.current 不参与闭包捕获——它是个可变指针,读写都不依赖闭包快照真正容易被忽略的点在于:闭包不是 React “引入”的问题,而是 JavaScript 执行模型的自然结果;React 的所有 hook 机制,都是在顺应并约束这个模型。你以为在“修复闭包”,其实是在学会和它共处。