当前位置: 首页 > news >正文

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

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.lryc.cn/news/166074.html

相关文章:

  • input子系统框架、外设驱动开发
  • Google Chrome 浏览器以全屏模式打开
  • 安装torch113、cuda116并运行demo【Transformer】
  • 基于scRNA-seq的GRN分析三阴性乳腺癌的肿瘤异质性
  • Python:二进制文件实现等间隔取相同数据量并合并
  • python使用openvc库进行图像数据增强
  • 如何利用Api接口获取手机当前的网络位置信息
  • vue-elementPlus自动按需导入和主题定制
  • idea中dataBase模板生成
  • pc端测试手机浏览器运行情况,主要是测试硬件功能
  • 软件概要设计-架构真题(二十五)
  • CSDN发文表情包整理
  • springBoot对接Apache POI 实现excel下载和上传
  • 定积分的计算:牛顿-莱布尼茨公式
  • shell脚本之case 的用法
  • 第3章 helloworld 驱动实验(iTOP-RK3568开发板驱动开发指南 )
  • 基于PyTorch使用LSTM实现新闻文本分类任务
  • Flutter插件的制作和发布
  • 【JAVA】异常
  • 合同矩阵充要条件
  • 数据分析三剑客之Pandas
  • Spring Boot自动装配原理
  • VMware Workstation虚拟机网络配置及配置自动启动
  • 智能语音机器人竞品调研
  • 【操作系统】进程的概念、组成、特征
  • 大二第二周总结
  • JDK、eclipse软件的安装
  • 235. 二叉搜索树的最近公共祖先 Python
  • Apollo介绍和入门
  • 一文看懂Oracle 19c OCM认证考试(需要Oracle OCP证书)