最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 使用 React Hooks 时需要注意过时的闭包!

    正文概述 掘金(前端小智)   2021-02-25   635

    Hooks 简化了 React 组件内部状态和副作用的管理。 此外,可以将重复的逻辑提取到自定义 Hooks 中,以在整个应用程序中重复使用。

    Hooks 严重依赖于 JS 闭包。这就是为什么 Hooks 如此具有表现力和简单,但是闭包有时很棘手。

    使用 Hooks 时可能遇到的一个问题就是过时的闭包,这可能很难解决。

    让我们从过时的装饰开始。 然后,看看到过时的闭包如何影响 React Hooks,以及如何解决该问题。

    1.过时的闭包

    工厂函数 createIncrement(incBy) 返回一个incrementlog函数的元组。 调用时,increment()函数将内部value增加incBy,而log()仅打印一条消息,其中包含有关当前value的信息:

    function createIncrement(incBy) {
      let value = 0;
    
      function increment() {
        value += incBy;
        console.log(value);
      }
    
     const message = `Current value is ${value}`; function log() { console.log(message); }  
      return [increment, log];
    }
    
    const [increment, log] = createIncrement(1);
    increment(); //  1
    increment(); //  2
    increment(); //  3
    // 不能正确工作!
    log();       //  "Current value is 0"
    

    [increment, log] = createIncrement(1)返回一个函数元组:一个函数增加内部值,另一个函数记录当前值。

    然后,increment()的3次调用将 value递增到3。

    最后,log()调用打印消息是 Current value is 0,这有点出乎意料的,因为此时 value3 了。

    log()是一个过时的闭包。闭包 log()捕获了值为 "Current value is 0"message 变量。

    即使 value 变量在调用increment()时被增加多次,message变量也不会更新,并且总是保持一个过时的值 "Current value is 0"

    2.修复过时的闭包

    修复过时的log()问题需要关闭实际更改的变量:value的闭包。

    我们将语句 const message = ...; 移动到 log() 函数内部:

    function createIncrement(incBy) {
      let value = 0;
    
      function increment() {
        value += incBy;
        console.log(value);
      }
    
      function log() {
     const message = `Current value is ${value}`;    console.log(message);
      }
      
      return [increment, log];
    }
    
    const [increment, log] = createIncrement(1);
    increment(); //  1
    increment(); //  2
    increment(); //  3
    // Works!
    log();       // "Current value is 3"
    

    现在,在调用了 3 次 increment() 函数之后,调用 log() 记录了实际value"Current value is 3"

    3. Hooks 中的过时闭包

    ### 3.1 useEffect()

    我们来看一下使用useEffect() 过时闭包的常见情况。

    在组件<WatchCount>中,useEffect() 中每2秒记录一次count的值

    function WatchCount() {
      const [count, setCount] = useState(0);
    
      useEffect(function() {
        setInterval(function log() {
          console.log(`Count is: ${count}`);
        }, 2000);
      }, []);
    
      return (
        <div> {count} <button onClick={() => setCount(count + 1) }> Increase </button> </div>
      );
    }
    

    打开事例(codesandbox.io/s/stale-clo…)

    并点击几次增加按钮。然后看看控制台,每2秒出现一次Count is: 0,尽管count状态变量实际上已经增加了几次。

    使用 React Hooks 时需要注意过时的闭包!

    为什么会这样?

    第一次渲染时,状态变量count初始化为0

    组件安装后,useEffect()调用 setInterval(log, 2000)计时器函数,该计时器函数计划每2秒调用一次log()函数。 在这里,闭包log()捕获到count变量为0

    之后,即使在单击Increase按钮时count增加,计时器函数每2秒调用一次的log(),使用count的值仍然是0log()成为一个过时的闭包。

    解决方案是让useEffect()知道闭包log()依赖于count,并在count改变时正确处理间隔的重置

    function WatchCount() {
      const [count, setCount] = useState(0);
    
      useEffect(function() {
        const id = setInterval(function log() {
          console.log(`Count is: ${count}`);
        }, 2000);
        return function() {
          clearInterval(id);
        }
     }, [count]);
      return (
        <div>
     {count}
     <button onClick={() => setCount(count + 1) }>
     Increase
     </button>
     </div>
      );
    }
    

    正确设置依赖项后,一旦count发生变化,useEffect()就会更新闭包。

    3.2 useState()

    <DelayedCount>组件有1个button ,以1秒延迟异步增加计数器。

    function DelayedCount() {
      const [count, setCount] = useState(0);
    
      function handleClickAsync() {
        setTimeout(function delay() {
          setCount(count + 1);
        }, 1000);
      }
    
      return (
        <div> {count} <button onClick={handleClickAsync}>Increase async</button> </div>
      );
    }
    

    现在打开演示(codesandbox.io/s/use-state… 快速单击2次按钮。 计数器仅更新为1,而不是预期的2

    每次单击setTimeout(delay, 1000)将在1秒后执行delay()delay()此时捕获到的 count0

    两个delay()都将状态更新为相同的值:setCount(count + 1) = setCount(0 + 1) = setCount(1)

    这是因为第二次单击的delay()闭包中已捕获了过时的count变量为0

    为了解决这个问题,我们使用函数式方法setCount(count => count + 1)来更新count状态

    function DelayedCount() {
      const [count, setCount] = useState(0);
    
      function handleClickAsync() {
        setTimeout(function delay() {
     setCount(count => count + 1);    }, 1000);
      }
    
      function handleClickSync() {
        setCount(count + 1);
      }
    
      return (
        <div>
     {count}
     <button onClick={handleClickAsync}>Increase async</button>
     <button onClick={handleClickSync}>Increase sync</button>
     </div>
      );
    }
    

    打开演示(codesandbox.io/s/use-state… 再次快速单击按钮2次。 计数器显示正确的值2

    当一个返回基于前一个状态的新状态的回调函数被提供给状态更新函数时,React确保将最新的状态值作为该回调函数的参数提供

    setCount(alwaysActualStateValue => newStateValue);
    

    这就是为什么在状态更新过程中出现的过时装饰问题可以通过函数这种方式来解决。

    4.总结

    当闭包捕获过时的变量时,就会发生过时的闭包问题。

    解决过时闭包的有效方法是正确设置React钩子的依赖项。或者,在失效状态的情况下,使用函数方式更新状态。

    ~完,我是小智,我要去刷碗了。


    代码部署后可能存在的BUG没法实时知道,事后为了解决这些BUG,花了大量的时间进行log 调试,这边顺便给大家推荐一个好用的BUG监控工具 Fundebug。

    原文:https://dmitripavlutin.com/react-hooks-stale-closures/

    交流

    文章每周持续更新,可以微信搜索「 大迁世界 」第一时间阅读和催更(比博客早一到两篇哟),本文 GitHub github.com/qq449245884… 已经收录,整理了很多我的文档,欢迎Star和完善,大家面试可以参照考点复习,另外关注公众号,后台回复福利,即可看到福利,你懂的。


    起源地下载网 » 使用 React Hooks 时需要注意过时的闭包!

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元