核心问题:为什么会拿不到最新值?
React 的闭包问题本质上是 JavaScript 闭包机制与 React 渲染机制的结合产物。当你在 useEffect
或事件处理函数中访问 state 时,实际上访问的是创建该闭包时的"快照"值,而不是当前最新的 state 值。
根本解决思路:转变思维模式
核心理念:拥抱纯函数思维
React 函数组件的本质是纯函数,每次渲染都是一次完整的函数执行。你需要在脑海中建立这样的图景:React 的渲染和更新就是在一轮又一轮地执行纯函数,每一轮函数组件执行都是在拿着输入计算输出。
最新值是你一步一步计算出来的,既然是你自己计算出来的,就不存在拿不到的情况。
实践示例对比
ini
// ❌ 错误写法:试图获取异步更新后的值
const [count, setCount] = useState(0);
useEffect(() => {
setCount(1);
var newCount = count; // 这里拿不到最新值,因为闭包捕获的是旧值
}, []);
// ✅ 正确写法1:直接使用计算的值
const [count, setCount] = useState(0);
useEffect(() => {
const v = 1;
setCount(v);
var newCount = v; // 这样就拿到了最新值
}, []);
// ✅ 正确写法2:使用函数式更新
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(prev => {
const newCount = prev + 1;
// 在这里可以直接使用 newCount
console.log("现在打印的就是最新值", newCount);
return newCount;
});
};
七大最佳实践原则
1. 控制组件复杂度
一个组件不要写太长。复杂的组件难以理解和维护,也更容易出现闭包问题。
2. 减少 useState 和 useEffect 的使用
能不写 useState
和 useEffect
就不要写。过度使用这些 Hook 会增加状态管理的复杂性。
3. 优先使用派生状态
能够从一个 state 派生出来的其他值,就不要声明成独立的 state。从原始状态计算出来即可,这样可以避免状态同步问题。
scss
// ❌ 避免
const [count, setCount] = useState(0);
const [doubleCount, setDoubleCount] = useState(0);
// ✅ 推荐
const [count, setCount] = useState(0);
const doubleCount = count * 2; // 直接计算派生值
4. 使用事件监听替代 useEffect
能够写成事件监听的回调函数,就不要把 useEffect
当作 Vue 里面的 watch
使用。
5. 采用现代状态管理库
一定要使用 React Query 等现代状态管理库,它们能够更好地处理异步状态和缓存。
关键洞察:事实上,对于前端逻辑不复杂的应用,一旦使用 React Query,连状态管理都不用自己操心了,更没有什么拿不到最新值的问题。
6. 条件渲染逻辑上提
如果代码中有 if else
判断,要把条件判断提升到父组件中,保证子组件只处理一种情况。避免在一个组件里既写 if else
又写复杂逻辑。
如果在子组件里写 if else
,再加上 useEffect
,每个 useEffect
里面的情况会变得非常复杂。不到万不得已不要使用 useEffect
。
react
// ❌ 错误写法:组件内部处理多种情况
const Wrong: FC<{count: number | null}> = ({count}) => {
// 这里可能报错,count 可能是 null
// 而且逻辑复杂了,这个组件内部每个地方都要加上 if else,很麻烦
const newCount = count + 1;
}
// ✅ 正确写法:条件判断上提到父组件
const Father: FC<{count: number | null}> = ({count}) => {
if (count == null) return null;
return <Child count={count} />
}
const Child: FC<{count: number}> = ({count}) => {
// 完美,在这个组件里不用考虑 count 是 null 的情况
const newCount = count + 1;
}
7. 建立正确的心理模型
React 渲染就是纯函数的反复执行,每次执行都是完整的计算过程。理解这一点,你就能从根本上避免闭包陷阱。
总结
React 闭包问题的根本解决方案不是技术技巧,而是思维转变。当你真正理解 React 函数组件的纯函数本质,并按照上述七大原则进行开发时,闭包问题会自然消失。记住:最新值是你计算出来的,不是你获取的。
顺便推广一下我写的React插件:juejin.cn/post/752239...