React函数式组件,异步操作导致更新旧的数据状态的问题

在使用React Hooks开发组件的过程中,遇到了这样的一个问题,看一下示例代码:

import React, { useState } from 'react'
export default ()=>{
  const [num, setNum] = useState(0);
  const handleClick = ()=>{
    setNum(num+1);
    setTimeout(() => {
      num
    }, 1000);
  }
  return <div>
    <h1 onClick={handleClick}>{num}</h1>
  </div>
}

为了方便说明问题,这里使用setTimeOut实现了异步的效果,原有代码其实是一个异步请求,但是原理是一样的。

代码很简单,大致的流程就是,定义了一个handClick 的回调函数,在函数内部,更新了num,然后设置了一个定时器,一秒之后访问num.

当第一次点击 h1 元素的时候,将num 的值加一,并为 1 , 并将状态更新。一秒后,执行setTimeOut 回调时,num的值仍然是 0。

当时碰到这个问题,很疑惑,认为这个和下面的代码其实没区别,为什么下面代码,在异步回调中就可以访问到最新的a的值,但是上面的就不行了呢?
React函数式组件,异步操作导致更新旧的数据状态的问题_第1张图片

后面来看,其实我忽略了很大的一个细节,那就是JS的中的内存管理。

我们都知道,在JS中,当我们声明一个变量的时候,会为其自动分配一个内存空间,当我们的程序中不再需要这个变量的时候,内存空间会被回收♻️,即我们常说的垃圾回收。

从这个角度来看,我们分析一下以上两端代码最大的区别在哪里:

首先看一下第一段,我们借助React Hooks 声明了两个变量,num 以及 setNum,而后我们在回调中,调用了setNum,将num+1 并更新到状态中。这一方法调用会触发React组件的更新,组件内部代码就会被重新

执行一次,包括第一行代码,对 num 以及 setNum的变量声明,也就是说,现在 num 和组件首次渲染时候的 num 分别对应了不通的内存空间,是两个完全不同的变量。

上面我们提到说,在JS的内存管理中,当变量不再需要的时候,会被销毁,内存空间被回收。回过头来看下我们的代码,在异步的回调中,我们访问了num, 所以这个num,也就是第一次声明的,值为 0 的num会常驻内从中,直到在一秒后执行回调的之后,这个变量才会被销毁,所以我们访问的这个变量的值,也就是 0 了。

至于第二段代码,其实就很明显了,变量a始终是变量a,并没有被再次声明。

那怎么解决上面的问题,在异步的回调中访问到最新的状态呢? 使用ref 就可以了

import React, { useState, useRef } from 'react'
export default ()=>{
  const [num, setNum] = useState(0);
  const numRef = useRef(num);
  numRef.current  = num;
  const handleClick = ()=>{
    setNum(num+1);
    setTimeout(() => {
      console.log(numRef.current);
    }, 1000);
  }
  return <div>
    <h1 onClick={handleClick}>{num}</h1>
  </div>
}

至于为什么用 useRef 就可以解决问题呢? 在官方文档中,已经讲的很清楚了,简单总结一下就是:

  • uesRef 返回的对象将在组件的整个生命周期内保持。
  • ref 不仅仅用于访问DOM节点,他其实是一个通用容器,其 current 属性是可变的,可以保存任何值,类似于类上的实例属性。

你可能感兴趣的:(javascript,react)