理解 React Hooks 的 Capture Value 特性

点击上方“全栈前端精选“,回复“1”进交流群

加入我们一起学习,天天进步

由于刚使用 React hooks 不久,对它的脾气还拿捏不准,掉了很多次“坑”;这里的 “坑” 的意思并不是说 React hooks 的设计有问题,而是我在使用的时候,因为还没有跟上它的理念导致的一些问题。

在读了一些文章后,大致是找到自己总是掉坑的原因了 —— 没理解 React Hooks 中的 「Capture Value」 特性。

本文就以简单的示例来解释这个特性所产生的现象,对理解 Capture Value 特性做一个补充。

1、状态值为什么不是最新的?

  • 官方相关 issue:Why am I seeing stale props or state inside my function?

“这个 effects 取的值怎么不是最新的?!”这个疑惑可以说是在使用 React Hooks 时经常遇到的疑问。

在下列代码中,当你点击按钮 3s 后,alert 显示的数值却是 3s 前的 count 变量 —— 即无法获取最新的值,获取的值是过去某个时刻的:

import React, { useState, useCallback } from "react";
 import ReactDOM from "react-dom";
 
 function Example() {
   const [count, setCount] = useState(0);
 
   const handleAlertClick = useCallback(()=>{
     setTimeout(() => {
       alert('You clicked on: ' + count);
     }, 3000)
   }, [count]);
 
   return (
     <div>
       <p>You clicked {count} times</p>
       <button onClick={() => setCount(count + 1)}>
         增加 count
       </button>
       <button onClick={handleAlertClick}>
         显示 count
       </button>
     </div>
   );
 }
 const rootElement = document.getElementById("root");
 ReactDOM.render(<Example />, rootElement);

示例代码:https://codesandbox.io/s/k5pmk0omx7

「具体操作步骤」

  • 当我们先点击 显示 按钮,在 3s 后(模拟耗时任务)会出现弹层

  • 在这 3s 期间快速点击 增加 count 按钮

  • 3s 后看到的弹层计数仍旧为 0.

3s 后看到的弹层计数仍旧为 0

2、解释

这是官方特意设置的机制,官方原文是:「This prevents bugs caused by the code assuming props and state don’t change」;(强行翻译一下,大概意思是:「防止因 React 认为 props 或者 state 没有变更而引起的 bug」

为了理解官方这么设定的意图,将上面代码稍微修改一下:

  • 去掉 显示 count 按钮

  • 增加一个 减少 count 的按钮

  • 使用 useEffect 代替 useCallback,让每次更改 count 都会弹窗

...
useEffect(()=>{
    setTimeout(() => {
      alert('count: ' + count);
    }, 3000)
  }, [count]);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>
        增加 count
      </button>
      <button onClick={() => setCount(count - 1)}>
        减少 count
      </button>
    </div>
  );
}
...

我们先点击一次 增加 count,然后再紧接着点击一次 减少 count

  • 如果不是按照官方的机制设置,那么我们看到的两次弹层显示的 count 数值都是 0 —— 很明显这不是我们想要的

  • 还好实际情况不是这样,会先显示 1,然后显示 0

会先显示 1,然后显示 0

总结起来,这个现象其实就是文章 精读《useEffect 完全指南》 所提及的 「Capture Value」 特性(可以自行前往原文了解更多细节)

3、扩展:如何获取即刻的 count 变量

回到原来的问题,倔强如我,「我就是想要在 3s 后获取的是此时此刻的 count 变量,而不是我 3s 前点击时的 count 值」,该怎么操作?

官方给出的解决方案是,每次改变 count 的时候,将其放在 ref 类型的变量里即可。

修改一下原来的代码:

  const countRef = useRef(null);
  const handleAlertClick = useCallback(
    () => {
      setTimeout(() => {
        alert("You clicked on: " + countRef.current);
      }, 3000);
    },
    [count]
  );

  return (
    <div>
      <p>You clicked {count} times</p>
      <button
        onClick={() => {
          countRef.current = count + 1;
          setCount(count + 1);
        }}
      >
        增加 count
      </button>
      <button onClick={handleAlertClick}>显示 count</button>
    </div>
  );

更改过后的代码运行后,3s 后 alert 显示的 count 变量就是你页面上所见到的样子了:

3s 后 alert 显示的 count 变量就是最新的 value

ref 类型的变量通常是用来存储 DOM 元素引用,但在 react hooks 中,它可以存放任何可变数据,就好比类实例属性一样,具体参考 Is there something like instance variables?

这等操作,其实就是借助 ref 类型变量绕过 「Capture Value」 特性来达到目的。

4、总结

援引文章 精读《useEffect 完全指南》 中对 Capture Value 概念的解释:「每次 Render 的内容都会形成一个快照并保留下来,因此当状态变更而 Rerender 时,就形成了 N 个 Render 状态,而每个 Render 状态都拥有自己固定不变的 Props 与 State」

通过这个示例,相信会比较容易地理解 「Capture Value」 特性,并如何使用 ref 来暂时绕过它。在知道并理解这个特性后,有助于进一步熟悉了 React Hooks 的运行机制,减少掉坑的次数。

这里罗列几篇文章,方便自检是否掌握了这个概念:

  • 通过 React Hooks 声明式地使用 setInterval:文章采用循序渐进的示例来解释问题。探索如何让 setIntervalHooks 和谐地玩耍,为什么是这种方式,以及这种方式给你带来了什么新能力。

  • How to get the previous props or state?: 如何获取变更前的 props 和 state ?官网提供的 useRef 来解决,也有人针对它进行了封装(How to compare oldValues and newValues on React Hooks useEffect?)

最后,如果你觉得这篇内容对你挺有启发, 可以帮我:

  1. 点个「在看」,让更多的人也能看到这篇内容。

  2. 关注「全栈前端精选」,掌握前端面试重难点,公众号后台回复「1」和小伙伴们畅聊技术。

欢迎评论区留下你的精彩评论~

觉得文章不错可以分享到朋友圈让更多的小伙伴看到哦~

客官!在看一下呗

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值