React 要点 函数式更新 2
React 中最容易踩坑的地方之一 :闭包问题和函数式更新
通过具体示例详细解释闭包问题,以及函数式更新如何解决它。。
闭包问题是什么?(具体示例)
假设这个计数器组件:
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
// 问题代码:直接使用闭包中的count
setTimeout(() => {
setCount(count + 1);
}, 3000);
};
return (
<div>
<p>当前值: {count}</p>
<button onClick={handleClick}>3秒后+1</button>
</div>
);
}
操作步骤:
- 初始状态:count = 0
- 快速点击按钮 3 次(在 3 秒内连续点击)
- 等待 3 秒后查看结果
预期结果:
count 应该变成 3(因为点了 3 次)
实际结果:
count 只变成 1
为什么会这样?
-
闭包陷阱:
- 每次点击时,
handleClick
函数都捕获了当时的count
值 - 第一次点击:捕获 count=0 →
setCount(0 + 1)
- 第二次点击:捕获 count=0 →
setCount(0 + 1)
- 第三次点击:捕获 count=0 →
setCount(0 + 1)
- 每次点击时,
-
状态更新是异步的:
- React 不会立即更新状态
- 3 次点击都在同一个渲染周期内,使用的都是初始状态值
函数式更新如何解决?
修改为函数式更新:
const handleClick = () => {
setTimeout(() => {
// 使用函数式更新
setCount((prev) => prev + 1);
}, 3000);
};
现在的结果:
- 无论点击多少次,3 秒后都会正确累加
- 点击 3 次 → count=3
为什么函数式更新能解决闭包问题?
-
不依赖闭包中的值:
// 直接更新:依赖外部变量 count(闭包陷阱) setCount(count + 1); // 函数式更新:不依赖外部变量 setCount((prev) => prev + 1);
-
React 保证提供最新值:
- 当 React 执行状态更新时
- 它会将当前最新状态作为参数传入更新函数
- 无论何时调用,
prev
总是最新的状态值
执行过程对比
问题代码的执行流程:
点击1: setCount(0 + 1) // 闭包count=0
点击2: setCount(0 + 1) // 闭包count=0 ❌
点击3: setCount(0 + 1) // 闭包count=0 ❌
结果: count=1 // 三次都设置成1
函数式更新的执行流程:
点击1: setCount(prev => prev + 1) // 排队更新函数1
点击2: setCount(prev => prev + 1) // 排队更新函数2
点击3: setCount(prev => prev + 1) // 排队更新函数3
React依次执行:
执行函数1: prev=0 → 0+1=1
执行函数2: prev=1 → 1+1=2
执行函数3: prev=2 → 2+1=3
结果: count=3 ✅
其他常见闭包问题场景
1. 事件监听器
useEffect(() => {
const handleScroll = () => {
// 这里获取的是初始状态
console.log(count);
};
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []);
2. setInterval 定时器
useEffect(() => {
const timer = setInterval(() => {
// 永远输出初始值
console.log(count);
}, 1000);
return () => clearInterval(timer);
}, []);
解决方案总结
场景 | 错误写法 | 正确写法 |
---|---|---|
状态更新 | setCount(count + 1) | setCount(prev => prev + 1) |
事件监听 | 直接使用状态 | 使用 ref 保存最新值 |
定时器 | 直接使用状态 | 结合 useRef+useEffect |
函数式更新的本质:
通过让 React 提供最新状态值,而不是依赖闭包中的过期值,从根本上解决了状态更新的闭包问题。