react组件和hooks中使用settimeout取值不是最新值问题

606 阅读2分钟

react组件和hooks中使用settimeout取值不是最新值问题

问题描述

在react中直接使用settimeout或者useEffect等hooks中使用settimeout来取值的时候,总是取到上一次更新的值而不是最新的值(不光是settimeout还有promise等中)。

代码示例

import React, { useEffect, useRef, useState } from 'react';

export default function Value() {
  const [num, setNum] = useState(0);
  const numRef = useRef();

  const handleClick = () => {
    setNum(num + 1);
  };

  setTimeout(() => {
    console.log(num, '组件中的值', numRef.current);
  }, 3000);

  useEffect(async () => {
    numRef.current = num;
    setTimeout(() => {
      console.log(num, 'hooks中的值', numRef.current);
    }, 3000);

    new Promise((resolve) => {
      setTimeout(() => {
        resolve(num);
      }, 3000);
    }).then(() => {
      console.log(num, 'promise中的', numRef.current);
    });
  }, [num]);

  return (
    <div>
      <button onClick={handleClick}>点击</button>
      <div>{num}</div>
    </div>
  );
}

上述代码中添加一个一个按钮,每次点击都会更新num的值,然后在组件中和useEffect中通过settimeout延迟打印更新的num值,当我快速点击5次后页面的值随着更新到5,但是当开始打印num值时打印的值从1、2、3、4、5依次打印并没有获取到最新值。如下图:
在这里插入图片描述

问题原因

因为在react中state数据的改变都遵循一个原则:state指向的内容是不可变的

因此state改变时并不是当前内容本身改变了,而是state指向了新的内容对象。原内容对象则会在没有被任何变量引用的情况下会自动被释放。

所以上述问题是因为当state数据改变的时候它本身指向了新的内容对象,但是因为闭包的原因settimeout中还保持着对上一个内容对象的引用,所以settimeout中打印的值不是state中最新的值,而是之前的旧值。

解决方法

解决方法可以利用useRef这个hook,在每次num值进行改变时将最新的值保存起来,在需要使用的时候直接使用useRef保存的值即可,具体可看代码实例中的打印,这里不再赘述。

特性扩展

既然react框架本身为我们在promise等操作中为我们保存了上次旧值的引用,那么我们在某些场景下就可以直接使用这种特性来为我们解决开发问题比如: 在开发时当某个str值改变时,根据str生成新的url地址然后发请求,由于接口性能、网络原因等导致我在连续发n次请求后,接口响应的先后顺序不可控,而我们只需要取最后一次响应的值进行操作,所以就可以使用这个特性去判断当前state中str值是不是最新值即可。