React Hook 的闭包陷阱
参考文章
1. 从react hooks“闭包陷阱”切入,浅谈react hooks
Hooks
Hooks 是 react 自 16.8 引入的新特性,使得开发者在摆脱 class 定义组件的同时,也能够进行状态管理。这样,react 组件完全进入函数式(FP)编程范式。既然进入了函数式编程的范畴,那么闭包(closure)就成了不可回避的话题,所以,react hooks 陷阱,说白了其实就是闭包特性。
Hooks 陷阱
从实例开始:
function Counter() {
const [count, setCount] = useState(0);
const handleClick = () => {
setTimeout(() => { // 这个 setTimeout 会引起闭包陷阱
setCount(count + 1);
}, 1000);
};
const handleReset = () => {
setCount(0);
};
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
<button onClick={handleReset}>Reset</button>
</div>
);
}
在上面的代码中, handleClick 的调用中有一个异步的执行,这就开启了一个宏任务,所以,这就是闭包陷阱产生的地方。相似的场景:
for (var i=0; i<5; i++) {
setTimeout(()=>{
console.log(i)
}, 0)
}
这是一段很经典的面试代码,如果按照上述的执行,其结果最终会打印 5 个 5 出来,而不是 0~4,其原因一样,异步任务产生的宏任务落后于主任务栈,那么该如何修改才能达到我们想要的效果?这里有两种思路:
// 一、将 var 改成 let,保证执行上下文的正确性
for (let i=0; i<5; i++) {
...
}
// 二、利用闭包特性
for (var i=0; i<5; i++ ) {
(function(i){
setTimeout(()=>{
console.log(i)
}, 0)
})(i);
}
而我们的 Hooks 闭包陷阱就是利用第二种方式解决的。
解决方案
先对比下有问题的写法和解决方案的写法:
// 问题写法
const handleClick = () => {
setTimeout(() => { // 这个 setTimeout 会引起闭包陷阱
setCount(count + 1);
}, 1000);
};
// 正确写法
setTimeout(() => {
setCount(currentCount => currentCount + 1);
}, 1000);
两者不同的地方是:错误写法中最终 setCount 的参数是一个具体的数值,而正确写法中传递的则是一个函数(这里基本上可以看出 react 对 FP 的编程范式的偏爱),以确保组件拿到的是最新的值。当然,这和 react 的系统有密切的关联,详情可以翻阅 react 的源码。
以上是对于 useState 这个 Hook 所产生的问题的 fix,那么对于 useEffect 也可能产生同样的问题,不过其解决方案更简单,只需要按照 react 的文档,注意补完 useEffect 的第二个参数即可

浙公网安备 33010602011771号