useGetState自定义hooks解决useState 异步回调获取不到最新值

news2025/1/23 7:02:06

setState 的两种传参方式

1、直接传入新值 setState(options);

const [state, setState] = useState(0);
setState(state + 1);

2、传入回调函数 setState(callBack);

const [state, setState] = useState(0);
setState((prevState) => prevState + 1); // prevState 是改变之前的 state 值,return 返回的值会作为新状态覆盖 state 值

useState 异步回调获取不到最新值及解决方案

通常情况下 setState 直接使用上述第一种方式传参即可,但在一些特殊情况下第一种方式会出现异常; 例如希望在异步回调或闭包中获取最新状态并设置状态,此时第一种方式获取的状态不是实时的,React 官方文档提到:组件内部的任何函数,包括事件处理函数和 Effect,都是从它被创建的那次渲染中被「看到」的,所以引用的值任然是旧的,最后导致 setState 出现异常:

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

const App = () => {
  const [arr, setArr] = useState([0]);

  useEffect(() => {
    console.log(arr);
  }, [arr]);

  const handleClick = () => {
    Promise.resolve().then(() => {
      setArr([...arr, 1]); // 此时赋值前 arr 为:[0]
    })
      .then(() => {
        setArr([...arr, 2]); // 此时赋值前 arr 为旧状态仍然为:[0]
      });
  }

  return (
    <>
      <button onClick={handleClick}>change</button>
    </>
  );
}

export default App;

// 输出结果
[0]
[0,1]
[0,2]

上面代码,App 组件实际也是个闭包函数,handleClick 里面引用着 arr,第一次 setArr 后 arr 的值确实更新了,我们也可以在上面输出结果中看到,但此次执行的 handleClick 事件处理函数作用域还是旧的,里面引用的 arr 仍然为旧的,导致第二次 setArr 后结果为 [0, 2]:

在 class 组件中我们可以使用 setState(options, callBack); 在 setState 的第二个参数回调函数中再次进行 setState,也不存在闭包作用域问题,但是 React Hook 中 useState 移除了 setState 的第二个参数,而且若嵌套太多也不佳;

解决方案1

const handleClick = () => {
    Promise.resolve().then(() => {
      setArr(prevState => [...prevState, 1]); // 这里也可以不改,使用第一中传参方式 setArr([...arr, 1]); 因为这里不需要获取最新状态
    })
      .then(() => {
        setArr(prevState => [...prevState, 2]); // 这里必须改成回调函数传参方式,否则会读取旧状态,导致异常
      });
  }


// 输出结果
[0]
[0,1]
[0,1,2]

我们发现用回调方式传参,输出结果是正确的。

解决方案2:

使用 useReducer 仿造类组件中的 forceUpdate 实现组件强制渲染; 注意: 此方案仅限于只有页面依赖该数据时适用,如果有类似 useEffect 等 hook 在监听该数据(示例中的 arr )时无法实时捕捉到变化

import React, { useState, useReducer } from 'react';

const App = () => {
  const [arr, setArr] = useState([0]);
  const [, forceUpdate] = useReducer(x => x + 1, 0);

  const handleClick = () => {
    Promise.resolve().then(() => {
      arr.push(1); // 如果这里也需要做一次渲染在改变状态后调用 forceUpdate() 即可
    })
      .then(() => {
        arr.push(2);
        forceUpdate();
      });
  }

  return (
    <>
      <h1>{arr.toString()}</h1>
      <button onClick={handleClick}>change</button>
    </>
  );
}

export default App;

解决方案3:

利用 ref ,state 发生改变同时将值映射到 ref ref 的改变不会触发页面更新,但在异步中一定能拿到最新值,所以需要在页面上用就使用 state,在异步逻辑中用就使用 ref

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

const App = () => {
  const [arr, setArr] = useState([0]);
  let ref = useRef();
  useEffect(() => {
    ref.current = arr;
    console.log(arr);
  }, [arr]);

  const handleClick = () => {
    Promise.resolve().then(() => {
      const now = [...ref.current, 1];
      ref.current = now;
      setArr(now);
    })
      .then(() => {
        setArr([...ref.current, 2]);
      });
  }

  return (
    <>
      <h1>{arr.toString()}</h1>
      <button onClick={handleClick}>change</button>
    </>
  );
}

export default App;

方案四,推荐方案:

上面例3这类方式可以自己封装一个 hooks 将 state 和 ref 进行关联,同时再提供一个方法供异步中获取最新值使用,例如:

const useGetState = (initVal) => {
  const [state, setState] = useState(initVal);
  const ref = useRef(initVal);
  const setStateCopy = (newVal) => {
    ref.current = newVal;
    setState(newVal);
  }
  const getState = () => ref.current;
  return [state, setStateCopy, getState];
}

const App = () => {
  const [arr, setArr, getArr] = useGetState([0]);
  useEffect(() => {
    console.log(arr);
  }, [arr]);

  const handleClick = () => {
    Promise.resolve().then(() => {
      setArr([...getArr(), 1]);
    })
      .then(() => {
        setArr([...getArr(), 2]);
      });
  }

  return (
    <>
      <h1>{arr.toString()}</h1>
      <button onClick={handleClick}>change</button>
    </>
  );
}

遇到useState 异步回调获取不到最新值的时候,推荐方案1和方案4,方案1解决更加简单,方案4解决更加完美,都推荐,看你的使用场景了

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/996355.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

BUUCTF reverse2 1

使用die查看文件信息&#xff0c;发现是ELF64位程序&#xff0c; 也就是说这是linux上的运行程序 再linux上运行 使用IDA64打开文件 F5 反编译 可以看到这里和flag进行对比 点击flag 点击这个7Bh&#xff0c;然后按r flag出来了 {hacking_for_fun}加上flag头提交 flag{h…

Python入门教程35:使用email模块发送HTML和图片邮件

smtplib模块实现邮件的发送功能&#xff0c;模拟一个stmp客户端&#xff0c;通过与smtp服务器交互来实现邮件发送的功能&#xff0c;可以理解成Foxmail的发邮件功能&#xff0c;在使用之前我们需要准备smtp服务器主机地址、邮箱账号以及密码信息。 #我的Python教程 #官方微信公…

用Canape配置VX1000的工程,在DA中绘制各个传感器目标的方法

参考本文档可帮助读者,快速安装VX1000软件,根据自己的需求,实现传感器目标在canape中DA的绘制。 介绍 Driver assistance (DA)系统是通过各种传感器(如视频、雷达、激光雷达等)获取有关车辆环境的信息。根据传感器对物体检测的结果(例如与前方车辆的距离)对驾驶员发…

《C++ Core Guidelines解析》:揭示现代C++最佳实践的深层原理

本书旨在深入解析C Core Guidelines&#xff0c;这是C社区中权威的编程指南。我们将探索其中所包含的现代C最佳实践&#xff0c;从底层原理和设计理念角度剖析其背后的思想。通过对Guidelines的逐条解析和实例说明&#xff0c;读者将深入理解如何编写更安全、高效和可维护的C代…

滑动窗口的最大值(双端队列,单调队列)

力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 class Solution {public int[] maxSlidingWindow(int[] nums, int k) {LinkedList<Integer> deque new LinkedList<>();//双端队列&#xff0c;存储单调队列的下标int ans[] new int[nu…

Android Studio 导入工程Gradle和JDK配置修改工程名称修改包名

一、Gradle 配置 经常遇到导入的工程却编译不过&#xff0c;很多情况下就是因为配置不一样导致有问题&#xff0c;主要有两个配置&#xff1a; Android Gradle Plugin Version Gradle Version 找一个能正常运行的项目&#xff0c;把它两配置成一样&#xff0c;一般都能解决问…

openssl命令行:RSA的用法-- 终极版

1、生成密钥 openssl genrsa -out test2048_priv.pem 2048 openssl rsa -pubout -in test2048_priv.pem -out test2048_pub.pem openssl genrsa -out test3072_priv.pem 3072 openssl rsa -pubout -in test3072_priv.pem -out test3072_pub.pem openssl genrsa -out test4096…

双系统 + Ubuntu20.04 + ros2 (foxy) git clone -b连接不成功的解决

一、问题描述 虚拟机已经跑通turtlebot3&#xff0c;能成功进行编译&#xff0c;进而执行自主避障&#xff0c;启动house地图&#xff0c;SLAM建图&#xff0c;SLAM导航等任务。但由于虚拟机加载gazebo模型太慢&#xff0c;且考虑到后面计划进行多机通讯&#xff0c;故配置双系…

解决table 操作栏塌陷的问题

1. el-table 塌陷 2. 解决办法 是通过查看官网,看见有一个重新布局的方法 https://element.eleme.cn/#/zh-CN/component/table 3. 代码实现 先将table 绑定ref 调用ref 方法 就ok了

Tomcat服务的部署及配置优化

文章目录 1. Tomcat的相关介绍1.1 Tomcat简介1.2 Tomcat的核心组件1.2.1 Web容器1.2.2 Servlet容器1.2.3 JSP容器 1.3 Tomcat的功能组件1.3.1 connector连接器1.3.2 container容器1.3.2.1 子容器及其相关功能 1.4 主要作用1.5 Tmocat处理请求的过程 2. Tomcata服务部署2.1 安装…

阻塞队列《——》特殊的队列(先进先出)

所谓的阻塞队列&#xff1a;就是带有阻塞特性的《——》线程安全的 如果队列为空&#xff0c;尝试出队列&#xff0c;就会阻塞等待&#xff0c;等到队列不为空为止如果队列为满&#xff0c;尝试入队列&#xff0c;也会阻塞等待&#xff0c;等到队列不为满为止 这个东西非常有…

制作立体图像实用软件:3DMasterKit 10.7 Crack

3DMasterKit 软件专为创建具有逼真 3D 和运动效果的光栅图片而设计&#xff1a;翻转、动画、变形和缩放。 打印机、广告工作室、摄影工作室和摄影师将发现 3DMasterKit 是一种有用且经济高效的解决方案&#xff0c;可将其业务扩展到新的维度&#xff0c;提高生成的 3D 图像和光…

企业架构LNMP学习笔记33

核心&#xff1a;负载均衡服务器有个转换&#xff0c;从外网转到内网的操作。返回的时候&#xff0c;从内网也要进行一次转换操作。 案例实现&#xff1a; 准备工作&#xff1a; ServerTypeIPserver04负载均衡调度服务器DS192.168.1.8&#xff08;对外访问的VIP&#xff09;&a…

网易Airtest全新推出:小型便携式集群解决方案!

1. 新旧版小型便携式集群大PK 先前我们针对中小型企业、工作室等&#xff0c;有推出过一款便携式机柜解决方案&#xff0c;业务同学只需要拎着一个像手提箱那么大的机柜&#xff0c;就可以在各个地方进行外场测试 &#xff0c;或者外出演示各种自动化场景。 但是之前的方案有…

ADC学习系列(一):ADC基础概念

本章主要是进行ADC的基础概念学习&#xff0c;从模拟和数字信号进行入手&#xff0c;分析各自的优缺点和应用场合&#xff0c;从而引出数模转换的重要性。紧接着提到了ADC部分最重要的奈奎斯特采样定理&#xff0c;了解采样频率和被测信号频率之间的关系。最后介绍了ADC的采样保…

领域驱动设计:DDD、中台和微服务的关系

文章目录 中台DDD、中台和微服务的协作模式中台建模 中台是抽象出来的业务模型&#xff0c;微服务是业务模型的系统实现&#xff0c;DDD作为方法论可以同时指导中台业务建模和微服务建设&#xff0c;三者相辅相成&#xff0c;完美结合。 中台 中台首先体现的是一种企业级的能力…

【Unity】rotation和Quaternion学习笔记

1.rotation 赋值 Quaternion可以为transform.rotation 赋值 2. 从正轴面向原点&#xff0c;顺时针旋转&#xff0c;角度正增加 正x轴面向原点&#xff0c;顺时针旋转&#xff0c;z正轴往下&#xff0c;rotation的x正增加。 3.rotation和Quaternion的关系 1.查询 2.实践 旋转…

基于SSM的精品酒销售管理系统

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;采用JSP技术开发 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#x…

SpringAOP的使用总结

B站 【尚硅谷新版SSM框架全套视频教程&#xff0c;Spring6SpringBoot3最新SSM企业级开发】https://www.bilibili.com/video/BV1AP411s7D7?p47&vd_source726decf3eb63273901caae35ad437124 AOP即面向切面编程,通过使用一定的技术将非核心方法抽离出来,放入统一的类中进行…

关于 ogbg-molhi数据集的个人解析

cs224w_colab2.py这个图属性预测到底咋预测的 dataset.meta_info.T Out[2]: num tasks 1 eval metric rocauc download_name …