React Hook 的闭包陷阱

参考文章

1. 从react hooks“闭包陷阱”切入,浅谈react hooks

2. 一文讲透 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 的第二个参数即可

posted @ 2023-08-08 10:45  shiweiqianju  阅读(200)  评论(0编辑  收藏  举报