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


    setState 的两种传参方式

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

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

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

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

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

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

    1. import React, { useState, useEffect } from 'react';
    2. const App = () => {
    3. const [arr, setArr] = useState([0]);
    4. useEffect(() => {
    5. console.log(arr);
    6. }, [arr]);
    7. const handleClick = () => {
    8. Promise.resolve().then(() => {
    9. setArr([...arr, 1]); // 此时赋值前 arr 为:[0]
    10. })
    11. .then(() => {
    12. setArr([...arr, 2]); // 此时赋值前 arr 为旧状态仍然为:[0]
    13. });
    14. }
    15. return (
    16. <>
    17. <button onClick={handleClick}>change</button>
    18. </>
    19. );
    20. }
    21. export default App;
    22. // 输出结果
    23. [0]
    24. [0,1]
    25. [0,2]

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

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

    解决方案1

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

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

    解决方案2:

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

    1. import React, { useState, useReducer } from 'react';
    2. const App = () => {
    3. const [arr, setArr] = useState([0]);
    4. const [, forceUpdate] = useReducer(x => x + 1, 0);
    5. const handleClick = () => {
    6. Promise.resolve().then(() => {
    7. arr.push(1); // 如果这里也需要做一次渲染在改变状态后调用 forceUpdate() 即可
    8. })
    9. .then(() => {
    10. arr.push(2);
    11. forceUpdate();
    12. });
    13. }
    14. return (
    15. <>
    16. <h1>{arr.toString()}</h1>
    17. <button onClick={handleClick}>change</button>
    18. </>
    19. );
    20. }
    21. export default App;

    解决方案3:

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

    1. import React, { useState, useRef, useEffect } from 'react';
    2. const App = () => {
    3. const [arr, setArr] = useState([0]);
    4. let ref = useRef();
    5. useEffect(() => {
    6. ref.current = arr;
    7. console.log(arr);
    8. }, [arr]);
    9. const handleClick = () => {
    10. Promise.resolve().then(() => {
    11. const now = [...ref.current, 1];
    12. ref.current = now;
    13. setArr(now);
    14. })
    15. .then(() => {
    16. setArr([...ref.current, 2]);
    17. });
    18. }
    19. return (
    20. <>
    21. <h1>{arr.toString()}</h1>
    22. <button onClick={handleClick}>change</button>
    23. </>
    24. );
    25. }
    26. export default App;

    方案四,推荐方案:

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

    1. const useGetState = (initVal) => {
    2. const [state, setState] = useState(initVal);
    3. const ref = useRef(initVal);
    4. const setStateCopy = (newVal) => {
    5. ref.current = newVal;
    6. setState(newVal);
    7. }
    8. const getState = () => ref.current;
    9. return [state, setStateCopy, getState];
    10. }
    11. const App = () => {
    12. const [arr, setArr, getArr] = useGetState([0]);
    13. useEffect(() => {
    14. console.log(arr);
    15. }, [arr]);
    16. const handleClick = () => {
    17. Promise.resolve().then(() => {
    18. setArr([...getArr(), 1]);
    19. })
    20. .then(() => {
    21. setArr([...getArr(), 2]);
    22. });
    23. }
    24. return (
    25. <>
    26. <h1>{arr.toString()}h1>
    27. <button onClick={handleClick}>changebutton>
    28. );
    29. }

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

  • 相关阅读:
    第三次裸心会
    C++之基于Winsock2封装UDPServer与UDPClient
    ThreadLocal:线程中的全局变量
    mac修改/etc/profile导致终端所有命令不可使用
    【web服务】nginx为什么这么受企业欢迎?看完这边文章你就懂了
    【文档+视频】Verdi基础教程
    go 函数
    Java http请求工具连接超时时间
    这次彻底搞定CSS层叠、优先级!
    Spring Cloud Alibaba【Sentinel控制台环境安装基于Linux、将应用接入Sentinel、流量控制概述、流控模式之直接模式、流控模式之关联模式 】(六)
  • 原文地址:https://blog.csdn.net/weixin_44786530/article/details/132792343