在 React 中,闭包陷阱常出现在 Hooks 和异步操作中,表现为捕获过期变量值。以下是常见场景及解决方案:
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
// 闭包捕获初始化时的 count=0
console.log(count); // 永远输出 0
}, 1000);
return () => clearInterval(timer);
}, []); // 空依赖数组
return ;
}
useEffect
的闭包捕获了初始渲染时的 count
,依赖数组为空导致不会更新闭包。useEffect(() => {
const timer = setInterval(() => {
console.log(count); // 每次渲染捕获最新 count
}, 1000);
return () => clearInterval(timer);
}, [count]); // 依赖数组声明 count
count
变化都会重新创建定时器。useRef
捕获最新值function Counter() {
const [count, setCount] = useState(0);
const latestCount = useRef(count);
useEffect(() => {
latestCount.current = count; // 实时更新 ref
});
useEffect(() => {
const timer = setInterval(() => {
console.log(latestCount.current); // 始终读取最新值
}, 1000);
return () => clearInterval(timer);
}, []); // 无需依赖 count
return ;
}
useRef
的 .current
属性是可变容器,可穿透闭包捕获最新值。const [count, setCount] = useState(0);
// 在异步操作中使用函数式更新
useEffect(() => {
const timer = setInterval(() => {
setCount(c => c + 1); // 直接基于最新状态更新
}, 1000);
return () => clearInterval(timer);
}, []);
setCount(c => c + 1)
直接访问最新状态,避免闭包陷阱。useCallback
冻结闭包const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
// 若依赖数组为空,此闭包永远捕获 count=0
console.log(count);
}, [count]); // 依赖数组声明 count
// 或使用函数式更新避免依赖
const stableHandler = useCallback(() => {
setCount(c => c + 1); // 不依赖 count
}, []);
function App() {
const [text, setText] = useState('');
useEffect(() => {
const handleClick = () => {
console.log(text); // 闭包捕获初始空字符串
};
document.addEventListener('click', handleClick);
return () => document.removeEventListener('click', handleClick);
}, []); // 空依赖导致闭包不更新
return setText(e.target.value)} />;
}
useRef
或声明依赖:const latestText = useRef(text);
useEffect(() => { latestText.current = text; }, [text]);
useEffect(() => {
const handleClick = () => {
console.log(latestText.current); // 读取最新值
};
document.addEventListener('click', handleClick);
return () => document.removeEventListener('click', handleClick);
}, []); // 依赖保持为空
function useCounter() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount(c => c + 1); // 使用函数式更新
}, []);
return { count, increment }; // increment 是稳定的
}
increment
的闭包问题。useEffect
、useCallback
、useMemo
中显式声明所有依赖。useRef
存储需要跨渲染周期保持最新的值。setState(c => c + 1)
直接访问最新值。useCallback
控制函数的重新创建,避免不必要的闭包变化。理解这些机制后,可大幅减少因闭包导致的 React 应用 Bug。