【React】闭包陷阱
所谓 React 中的闭包陷阱(Closure Trap),其实是 函数组件里的事件回调或副作用中,引用了过期的 state 或 props,导致逻辑异常。
🔹 1. 为什么 React 里会有闭包陷阱?
- React 函数组件 本质上是一个 render 函数。
- 每次渲染都会生成新的 props / state,以及新的 函数作用域。
- 如果某个回调函数(比如事件处理函数、
setTimeout
里的回调、useEffect
里的函数)引用了旧的 state/props,就会形成一个 闭包,而这个闭包“锁死”了旧的值。
🔹 2. 典型例子
import React, { useState } from "react";export default function Counter() {const [count, setCount] = useState(0);function handleClick() {setTimeout(() => {// ❌ 这里会“捕获”当时的 countalert(count);}, 3000);}return (<div><p>{count}</p><button onClick={() => setCount(count + 1)}>+1</button><button onClick={handleClick}>Show Count (after 3s)</button></div>);
}
👉 如果你点了两次 +1
(count=2),再点 Show Count
,等 3 秒后 alert 出来的却可能是 旧值 0/1。
原因:handleClick
生成的闭包里,保存的 count
还是旧的。
🔹 3. 解决办法
✅ 方法一:使用函数式更新(推荐)
setCount(prev => prev + 1);
👉 不依赖闭包里的 count
,而是总能拿到最新的值。
✅ 方法二:依赖数组正确写法(useEffect
)
useEffect(() => {const timer = setInterval(() => {console.log(count); // 每次渲染后闭包更新,打印最新值}, 1000);return () => clearInterval(timer);
}, [count]); // 依赖 count
👉 保证每次 count
变了,副作用都能捕获到新值。
✅ 方法三:使用 useRef
保存最新值
const countRef = useRef(count);useEffect(() => {countRef.current = count; // 每次更新时刷新 ref
});function handleClick() {setTimeout(() => {alert(countRef.current); // ✅ 永远能拿到最新值}, 3000);
}
👉 ref
不会因为重新渲染而丢失,适合存放“跨渲染保持的最新值”。
🔹 4. 总结
-
闭包陷阱原因:函数组件每次渲染都会创建新的作用域,回调函数可能捕获旧的 props/state。
-
常见场景:
setTimeout
、setInterval
、事件回调、异步请求回调、useEffect
。 -
解决办法:
- 函数式更新
setState(prev => ...)
- 正确声明依赖(
useEffect
) - 使用
useRef
存最新值
- 函数式更新