react-hooks 场景下,如何在异步回调里访问当前的状态值? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
justdoit123
V2EX    React

react-hooks 场景下,如何在异步回调里访问当前的状态值?

  •  
  •   justdoit123 2024 年 5 月 10 日 2695 次点击
    这是一个创建于 622 天前的主题,其中的信息可能已经有所发展或是发生改变。

    比如下面的代码,用一个简单 submitting 来说明(不是实际的业务需求,只是个演示)。

    如果真的要在回调里去读取当前的 submitting 状态,有什么方法吗?

    我知道有一个 useEffect hook ,去监听 submitting 状态的变更。但是感觉这样好麻烦,实际业务中,状态一多,就是一团麻的感觉。另外,有时候状态变更并不能很精准的表达实际发生的事情。就拿这个 submitting 来说。它可能是用户点击了 button 触发的,也可能是通过其它事件方式触发的,如果这些触发的事件也是业务需要考虑的,那简单的监听 submitting 变更似乎不能满足业务需求(可能还要加个类似 trigger by 的状态?)。

    import { useState } from "react"; export const DebugPage = () => { const [submitting, setSubmitting] = useState(false); const handleSubmit = () => { setSubmitting(true); // 用 setTimeout 模拟异步请求 setTimeout(() => { // 在异步回调的时候,这个 submitting 的状态依然是 false 。 // 这个我能理解。因为这个生成这个词法闭包上的时候 submitting 就是 false 。 // // 但是假如我需要在这里读取 submitting 当前的准确状态,要怎么做?有办法做到吗? console.log('submitting: ', submitting); setSubmitting(false); }, 1000); } return ( <button OnClick={handleSubmit}>{submitting ? 'Submitting' : 'Submit'}</button> ) } 

    不知道有没有描述清楚我的问题。谢谢各位~

    19 条回复    2024-05-11 10:26:33 +08:00
    sukinee
        1
    sukinee  
       2024 年 5 月 10 日
    useRef
    Leviathann
        2
    Leviathann  
       2024 年 5 月 10 日
    用 ref 记录 submitting ,每次 setState 的时候都同步修改 ref

    相当于 setState 只用作触发 rerender
    lisongeee
        3
    lisongeee  
       2024 年 5 月 10 日
    ```js
    const [submitting, setSubmitting] = useState(false);

    const OnClick= async () => {
    setSubmitting(true);
    const r = await fetch('/api/hello').then((r) => r.json());
    const latestValue = await new Promise<boolean>((res) => {
    setSubmitting((v) => (res(v), v));
    });
    console.log(latestValue)
    };
    ```
    weixind
        4
    weixind  
       2024 年 5 月 10 日
    当你想多处触发同一个 submitting 的时候思路已经有点偏了。state 状态要分离,应该有个 handleSubmitting 。或者直接用 useQuery 之类的东西。你提交的 loading 就是提交的 loading 。其他的 loading 和他样式可以一样,但是逻辑不要揉到一块。如果你觉得 state 多了会是一团乱麻。多处逻辑更改同一个 state 才更是一团乱麻。追踪变更都追踪不到。
    justdoit123
        5
    justdoit123  
    OP
       2024 年 5 月 10 日
    @lisongeee setSubmitting 可以传入一个函数来修改当前值这个我知道。需求是要在 callback 里就能读到状态的当前值。
    justdoit123
        6
    justdoit123  
    OP
       2024 年 5 月 10 日
    @Leviathann 这种情况真的只能加上 ref 了吗?捂脸
    Puteulanus
        7
    Puteulanus  
       2024 年 5 月 10 日
    有个邪教方法能取到
    setSubmitting 接受一个函数作为输入,你可以传一个 a => a+1 给它,在不知道 Submitting 值的情况下来给 Submitting 加一,换句话说这个函数肯定是能拿到你想要的“当前”值的,只是函数不一定同步执行
    所以有


    当然这种主要是写着好玩,常规做法你还是看看楼上的,这种代码写多了容易被 review 的掐死。。
    lisongeee
        8
    lisongeee  
       2024 年 5 月 10 日
    latestValue 不就是状态的当前值吗?

    你是没理解我使用的 async/await 吗?我直接发你贴的代码转换之后的完整代码吧

    ```tsx
    import { useState } from 'react';

    export const DebugPage = () => {
    const [submitting, setSubmitting] = useState(false);

    const handleSubmit = async () => {
    setSubmitting(true);

    // 用 setTimeout 模拟异步请求
    await new Promise((res) => setTimeout(res, 1000));

    // 但是假如我需要在这里读取 submitting 当前的准确状态,要怎么做?有办法做到吗?
    const latestSubmitting = await new Promise<boolean>((res) => {
    setSubmitting((v) => (res(v), v));
    });
    console.log(latestSubmitting);
    };

    return (
    <button OnClick={handleSubmit}>
    {submitting ? 'Submitting' : 'Submit'}
    </button>
    );
    };

    ```
    justdoit123
        9
    justdoit123  
    OP
       2024 年 5 月 10 日
    @weixind 这个 use-query 看着不错。


    关于 状态 还是 set 多不展开讨论。 我会问这个问题,是因为我在实现一个连续 timer 的需求。

    1. timer 一开始是停止的,有个按钮让用户点开始;再点一次就停止。
    2. 一个 timer 时间到了,需要提交数据,然后自动开始下一个 timer ,直到用户点停止为止。


    那么如何在一个 timer 数据提交后,自动触发下一个?直接调用 startTimer 肯定是不行的,因为里面包含的 state 都是“旧”的。

    我目前能想到的是通过 pubsub 来绕过这个问题,或者就像 1, 2 楼 说的用 ref 。
    justdoit123
        10
    justdoit123  
    OP
       2024 年 5 月 10 日
    @lisongeee 哇哦,还有这种用法。前面没有细看,明白了。
    justdoit123
        11
    justdoit123  
    OP
       2024 年 5 月 10 日
    @Puteulanus 谢谢~ 捂脸
    justdoit123
        12
    justdoit123  
    OP
       2024 年 5 月 10 日
    暂时采用 ref 的方式解决。把一些 handler 放入 ref ,类似这样:

    ```tsx
    const handlersRef = useRef<{
    submitLog?: typeof submitLog;
    startClock?: typeof startClock;
    }>({});
    ```
    codehz
        13
    codehz  
       2024 年 5 月 10 日   2
    我建议用一下 swr 这类请求封装好的吧,你这样滥用 swr 迟早会出事()
    另外正统的读 state 当前值的方法是传递一个函数给 setState(current => { /* play with current */ return current; })
    codehz
        14
    codehz  
       2024 年 5 月 10 日
    异步请求除了处理中,还有分页(无限滚动),缓存,失败,重试,自动刷新等需求,全部集合到一起,你就重新发明了 swr ,倒不如一开始就直接用
    yukinotech
        15
    yukinotech  
       2024 年 5 月 10 日
    @codehz setState(current => { /* play with current */ return current; }) 太艹了
    northquq
        16
    northquq  
       2024 年 5 月 11 日
    sh666
        17
    sh666  
       2024 年 5 月 11 日
    setSubmitting((prev) => {
    console.log('submitting: ', prev);
    return prev;
    });
    setSubmitting(false);
    rookie2luochao
        18
    rookie2luochao  
       2024 年 5 月 11 日
    建议用一下,react-query ,swr 这种支持 promise 异步 http 请求的库,用 useQuery, useMutation 这种 hook, 可以同步请求的 loading 状态,demo 请看:
    https://github.com/rookie-luochao/create-vite-app-cli/tree/master/template-react-ts#%E8%B0%83%E7%94%A8%E6%8E%A5%E5%8F%A3react-query-%E6%94%AF%E6%8C%81%E8%87%AA%E5%8A%A8loading%E5%92%8C%E6%8E%A5%E5%8F%A3%E8%AF%B7%E6%B1%82%E8%81%94%E5%8A%A8
    rookie2luochao
        19
    rookie2luochao  
       2024 年 5 月 11 日
    如果你不想用,而你的目标就是更好的控制 loading, react19 提供了一个新的 hook 来同步这种 loading ,叫 useTransition
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     3232 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 12:13 PVG 20:13 LAX 04:13 JFK 07:13
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86