一、问题现象
在 React 函数组件中,事件回调函数里获取的状态值不是最新值,而是初始值或旧值。
ts
const [count, setCount] = useState(0);
// 点击按钮时,期望打印最新值,实际却打印旧值
const handleClick = () => {
console.log(count); // 可能不是最新值!
};
二、根因分析
2.1 闭包的基本特性
JavaScript 闭包会捕获定义时的变量值:
ts
function createClosure() {
let value = 0;
return {
getValue: () => value, // 捕获 value 的引用
setValue: (v) => { value = v; }
};
}
const closure = createClosure();
closure.setValue(10);
console.log(closure.getValue()); // 10 ✅ 正常工作
2.2 React 组件的特殊情况
React 函数组件每次渲染都会创建新的函数作用域:
ts
// 第一次渲染
function MyComponent() {
const [count, setCount] = useState(0);
// 这次渲染时,count = 0
const handleClick = () => {
console.log(count); // 捕获 count = 0
};
return <button onClick={handleClick}>Click</button>;
}
// 第二次渲染(setCount(1) 后)
function MyComponent() {
const [count, setCount] = useState(1);
// 这次渲染时,count = 1
// 但是!如果 handleClick 没有重新创建
// 它仍然是旧的,捕获着 count = 0
}
2.3 问题示意

三、典型场景
场景 1:useEffect 依赖数组缺失
ts
// ❌ 错误示例
useEffect(() => {
const timer = setInterval(() => {
console.log(count); // 永远是 0
}, 1000);
return () => clearInterval(timer);
}, []); // 缺少 count 依赖
// ✅ 正确写法
useEffect(() => {
const timer = setInterval(() => {
console.log(count);
}, 1000);
return () => clearInterval(timer);
}, [count]); // 添加 count 依赖
场景 2:事件回调未更新
ts
// ❌ 错误示例
const handleClick = useRef(() => {
console.log(data); // 捕获旧的 data
}).current;
// ✅ 正确写法 1:使用 useCallback
const handleClick = useCallback(() => {
console.log(data);
}, [data]); // 依赖变化时重新创建
// ✅ 正确写法 2:使用 useRef 同步最新值
const dataRef = useRef(data);
useEffect(() => {
dataRef.current = data;
}, [data]);
const handleClick = () => {
console.log(dataRef.current); // 始终是最新的
};
场景 3:异步操作中的状态
ts
// ❌ 错误示例
const handleAsync = () => {
fetchData().then(() => {
console.log(items); // 可能是旧值
});
};
// ✅ 正确写法 1:使用 useRef
const itemsRef = useRef(items);
useEffect(() => {
itemsRef.current = items;
}, [items]);
const handleAsync = () => {
fetchData().then(() => {
console.log(itemsRef.current); // 始终是最新的
});
};
// ✅ 正确写法 2:使用函数式更新
const handleAsync = () => {
fetchData().then(() => {
setItems(prev => {
console.log(prev); // 获取最新值
return prev;
});
});
};
四、解决方案对比
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| 添加依赖 | 简单场景 | 直观简单 | 可能导致频繁重建 |
| useCallback | 事件处理 | 性能可控 | 需要维护依赖数组 |
| useRef 同步 | 复杂状态 | 避免重建 | 需要额外同步逻辑 |
| 函数式更新 | 状态更新场景 | 无需额外依赖 | 仅适用于 setState |
五、最佳实践
选择正确的策略

代码示例
ts
// ✅ 推荐:综合使用多种策略
function useLatestValue<T>(value: T) {
const ref = useRef(value);
useEffect(() => {
ref.current = value;
}, [value]);
return ref;
}
// 使用示例
function MyComponent() {
const [data, setData] = useState([]);
const dataRef = useLatestValue(data);
const handleEvent = useCallback(() => {
// 使用 ref 获取最新值
console.log(dataRef.current);
}, []); // 依赖数组可以为空
return <div onClick={handleEvent}>Click</div>;
}
六、总结
- 问题本质 闭包捕获的是定义时的值,不是调用时的值
- 触发条件 回调函数未随状态更新而重新创建
- 解决思路 让回调函数能获取到最新值