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

40 天前
 justdoit123

比如下面的代码,用一个简单 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>
  )
}

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

1246 次点击
所在节点    React
19 条回复
sukinee
40 天前
useRef
Leviathann
40 天前
用 ref 记录 submitting ,每次 setState 的时候都同步修改 ref

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


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


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

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


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

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

```tsx
const handlersRef = useRef<{
submitLog?: typeof submitLog;
startClock?: typeof startClock;
}>({});
```
codehz
40 天前
我建议用一下 swr 这类请求封装好的吧,你这样滥用 swr 迟早会出事()
另外正统的读 state 当前值的方法是传递一个函数给 setState(current => { /* play with current */ return current; })
codehz
40 天前
异步请求除了处理中,还有分页(无限滚动),缓存,失败,重试,自动刷新等需求,全部集合到一起,你就重新发明了 swr ,倒不如一开始就直接用
yukinotech
40 天前
@codehz setState(current => { /* play with current */ return current; }) 太艹了
northquq
39 天前
sh666
39 天前
setSubmitting((prev) => {
console.log('submitting: ', prev);
return prev;
});
setSubmitting(false);
rookie2luochao
39 天前
建议用一下,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
39 天前
如果你不想用,而你的目标就是更好的控制 loading, react19 提供了一个新的 hook 来同步这种 loading ,叫 useTransition

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1039545

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX