V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
justdoit123
V2EX  ›  React

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

  •  
  •   justdoit123 · 230 天前 · 1951 次点击
    这是一个创建于 230 天前的主题,其中的信息可能已经有所发展或是发生改变。

    比如下面的代码,用一个简单 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  
       230 天前
    useRef
    Leviathann
        2
    Leviathann  
       230 天前
    用 ref 记录 submitting ,每次 setState 的时候都同步修改 ref

    相当于 setState 只用作触发 rerender
    lisongeee
        3
    lisongeee  
       230 天前
    ```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  
       230 天前
    当你想多处触发同一个 submitting 的时候思路已经有点偏了。state 状态要分离,应该有个 handleSubmitting 。或者直接用 useQuery 之类的东西。你提交的 loading 就是提交的 loading 。其他的 loading 和他样式可以一样,但是逻辑不要揉到一块。如果你觉得 state 多了会是一团乱麻。多处逻辑更改同一个 state 才更是一团乱麻。追踪变更都追踪不到。
    justdoit123
        5
    justdoit123  
    OP
       230 天前
    @lisongeee setSubmitting 可以传入一个函数来修改当前值这个我知道。需求是要在 callback 里就能读到状态的当前值。
    justdoit123
        6
    justdoit123  
    OP
       230 天前
    @Leviathann 这种情况真的只能加上 ref 了吗?捂脸
    Puteulanus
        7
    Puteulanus  
       230 天前
    有个邪教方法能取到
    setSubmitting 接受一个函数作为输入,你可以传一个 a => a+1 给它,在不知道 Submitting 值的情况下来给 Submitting 加一,换句话说这个函数肯定是能拿到你想要的“当前”值的,只是函数不一定同步执行
    所以有


    当然这种主要是写着好玩,常规做法你还是看看楼上的,这种代码写多了容易被 review 的掐死。。
    lisongeee
        8
    lisongeee  
       230 天前
    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
       230 天前
    @weixind 这个 use-query 看着不错。


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

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


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

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

    ```tsx
    const handlersRef = useRef<{
    submitLog?: typeof submitLog;
    startClock?: typeof startClock;
    }>({});
    ```
    codehz
        13
    codehz  
       230 天前   ❤️ 2
    我建议用一下 swr 这类请求封装好的吧,你这样滥用 swr 迟早会出事()
    另外正统的读 state 当前值的方法是传递一个函数给 setState(current => { /* play with current */ return current; })
    codehz
        14
    codehz  
       230 天前
    异步请求除了处理中,还有分页(无限滚动),缓存,失败,重试,自动刷新等需求,全部集合到一起,你就重新发明了 swr ,倒不如一开始就直接用
    yukinotech
        15
    yukinotech  
       230 天前
    @codehz setState(current => { /* play with current */ return current; }) 太艹了
    northquq
        16
    northquq  
       230 天前
    sh666
        17
    sh666  
       230 天前
    setSubmitting((prev) => {
    console.log('submitting: ', prev);
    return prev;
    });
    setSubmitting(false);
    rookie2luochao
        18
    rookie2luochao  
       230 天前
    建议用一下,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  
       230 天前
    如果你不想用,而你的目标就是更好的控制 loading, react19 提供了一个新的 hook 来同步这种 loading ,叫 useTransition
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5730 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 24ms · UTC 06:26 · PVG 14:26 · LAX 22:26 · JFK 01:26
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.