美文网首页
react hook 的闭包陷阱

react hook 的闭包陷阱

作者: l1n3x | 来源:发表于2021-09-25 15:10 被阅读0次

    上一篇文章 https://www.jianshu.com/p/272c7d36021a 分析了关于变量作用域以及闭包的问题。这篇文章来看看如何应用它来分析 react hook 中的闭包陷阱。

    useEffect hook

    useEffect 是 react 推出用于取代生命周期函数的 hook。它接收两个参数。第一个参数为一个回调函数,它在元素渲染后会执行。第二个参数为可选的数组,它控制回调函数的执行。有 3 种可能的情况:

    1. 省略:回调函数在每次渲染之后都会执行。
    2. 空数组时: 只有在第一次渲染后才执行,即只执行一次。
    3. 包含状态的数组: 只有渲染后且当这些状态有改变时执行。

    在 react hook 中有个经典的闭包陷阱:

    import "./styles.css";
    import {useEffect, useState} from 'react'
    
    export default function App() {
      const [count, setCount] = useState(0)
      useEffect(()=>{
        setInterval(() => {
          setCount(count + 1)
        }, 1000);
      }, [])
      return (
           <p>count={count}</p>
      );
    }
    

    在代码中设置了一个定时函数,每隔 1s 将 count 的值加 1。并且将 useEffect 中的第二个参数设置为空数组。表示这个设置定时器的动作只进行一次。从理解来看,这种设置定时器的动作只需要进行一次即可。但是写过 react 的都知道,页面上 count 的值会一直保持 1,这显然不符合预期。

    why

    关于这个问题我在网上做了许多搜索,大部分只会提到这是一个过时闭包的问题,但没有人能将其讲清楚。实际上,正如我在上一篇文章中提到的,关于闭包问题,只需要记住这两点:

    1. 函数、控制块执行时会产生新的词法环境
    2. 当前词法环境找不到的变量会继续外外部词法环境寻找

    对于问题提到的这个问题,我们首先需要知道当某个组件需要更新时,总是会调用这个组件对应的函数或者 render 函数。而每次函数调用总会生成一个新的词法环境。但是,对于 useEffect 中的回调函数,它始终捕获第一个函数调用时生成的词法环境中的 count(因为它只会执行一次,没有机会捕获第二次,第三次函数执行产生的词法环境中的 count),因此被捕获的 count 的值始终为 0,而 count + 1 的值也始终是 1。对于这种情况,如果希望出现预期的效果,就需要 useEffect 中的函数多次运行,不断的捕获新的词法环境中的 count。将 useEffect 改为:

    useEffect(()=>{
      setInterval(() => {
          setCount(count + 1)
        }, 1000);
      }, [count])
    

    这样才能保证能捕获到正确的 count。当然由于 setInterval 多次运行,所以这里还需要一个 clearInterval 的操作。

    思考

    import "./styles.css";
    import {useEffect, useState} from 'react'
    
    export default function App() {
      const [count, setCount] = useState(0)
      useEffect(()=>{
        setInterval(() => {
          console.info(count)
        }, 1000)
      }, [])
      return (
        <button onClick={setCount}>click me!</button>
      );
    }
    

    如果多次点击按钮,控制台打印的 count 的值是否会发生改变呢?
    同样:

    (function (){
        let count = 1;
        setInterval(() => {
            count += 1;
            console.info(count)
        }, 1000)
    })()
    

    控制台打印的值是否会发生改变? 如果你能正确的判断这两个例子的结果并且说明原因,那么 react 中的闭包陷阱应该不会再难到你。

    相关文章

      网友评论

          本文标题:react hook 的闭包陷阱

          本文链接:https://www.haomeiwen.com/subject/ebjxnltx.html