专栏首页全栈前端精选理解 React Hooks 的 Capture Value 特性

理解 React Hooks 的 Capture Value 特性

由于刚使用 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?)

本文分享自微信公众号 - 全栈前端精选(isNealyang),作者:玄农

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2020-03-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 看完这篇,你也能把 React Hooks 玩出花

    React v16.7.0-alpha 中第一次引入了 Hooks 的概念,在 v16.8.0 版本被正式发布。React Hooks 在 React 中只是对...

    Nealyang
  • 从零实现一套属于自己的UI框架-发布到npm

    如今前端工程师的要求越来越高了,需要掌握的技术点越来越多了,会一些基本的前端技能完全适应不了快速变化的前端领域了。接下来我将从零实现一个自己的UI组件库并发布到...

    Nealyang
  • 大揭秘!“恐怖”的阿里一面,我究竟想问什么

    其实不得不说,找工作,真的七分实力,三分运气。不同的面试官有不同的看重点,所以千万不要为一次的滑铁卢而丢失信心。

    Nealyang
  • 你是一直认为 count(1) 比 count(*) 效率高么?

    MySQL count(1) 真的比 count(*) 快么? 反正同事们都是这么说的,我也姑且觉得对吧,那么没有自己研究一下究竟?如果我告诉你他们一样,你信么...

    java思维导图
  • 好问题:count(1)、count(*)、count(列)有什么区别?

    当表的数据量大些时,对表作分析之后,使用count(1)还要比使用count(*)用时多了!

    Java技术栈
  • Java自增自减运算符神坑笔试题

    mathor
  • MySQL count()函数及其优化count(1),count(*),count(字段)区别

    JavaEdge
  • 【Python】统计字符串中英文、空格、数字、标点个数

    题外话:今天打酱油的做了**数据挖掘工程师的在线笔试题,被打击了。 本文代码可在 这里 下载。 问题 在网上无意间看到这么一个题目:统计一个字符串中的中英文、空...

    Alan Lee
  • 执行COUNT(1)、COUNT(*) 与 COUNT(列名) 到底有什么区别?

    来源:blog.csdn.net/iFuMI/article/details/77920767

    好好学java
  • IRscope代码拆解一

    readLines()函数读入文本文件,结果好像是一个向量,文件中的每行是向量中的一个元素。

    用户7010445

扫码关注云+社区

领取腾讯云代金券