
比如下面的代码,用一个简单 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> ) } 不知道有没有描述清楚我的问题。谢谢各位~
1 sukinee 2024 年 5 月 10 日 useRef |
2 Leviathann 2024 年 5 月 10 日 用 ref 记录 submitting ,每次 setState 的时候都同步修改 ref 相当于 setState 只用作触发 rerender |
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) }; ``` |
4 weixind 2024 年 5 月 10 日 当你想多处触发同一个 submitting 的时候思路已经有点偏了。state 状态要分离,应该有个 handleSubmitting 。或者直接用 useQuery 之类的东西。你提交的 loading 就是提交的 loading 。其他的 loading 和他样式可以一样,但是逻辑不要揉到一块。如果你觉得 state 多了会是一团乱麻。多处逻辑更改同一个 state 才更是一团乱麻。追踪变更都追踪不到。 |
5 justdoit123 OP @lisongeee setSubmitting 可以传入一个函数来修改当前值这个我知道。需求是要在 callback 里就能读到状态的当前值。 |
6 justdoit123 OP @Leviathann 这种情况真的只能加上 ref 了吗?捂脸 |
7 Puteulanus 2024 年 5 月 10 日 |
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> ); }; ``` |
9 justdoit123 OP @weixind 这个 use-query 看着不错。 关于 状态 还是 set 多不展开讨论。 我会问这个问题,是因为我在实现一个连续 timer 的需求。 1. timer 一开始是停止的,有个按钮让用户点开始;再点一次就停止。 2. 一个 timer 时间到了,需要提交数据,然后自动开始下一个 timer ,直到用户点停止为止。 那么如何在一个 timer 数据提交后,自动触发下一个?直接调用 startTimer 肯定是不行的,因为里面包含的 state 都是“旧”的。 我目前能想到的是通过 pubsub 来绕过这个问题,或者就像 1, 2 楼 说的用 ref 。 |
10 justdoit123 OP @lisongeee 哇哦,还有这种用法。前面没有细看,明白了。 |
11 justdoit123 OP @Puteulanus 谢谢~ 捂脸 |
12 justdoit123 OP 暂时采用 ref 的方式解决。把一些 handler 放入 ref ,类似这样: ```tsx const handlersRef = useRef<{ submitLog?: typeof submitLog; startClock?: typeof startClock; }>({}); ``` |
13 codehz 2024 年 5 月 10 日 我建议用一下 swr 这类请求封装好的吧,你这样滥用 swr 迟早会出事() 另外正统的读 state 当前值的方法是传递一个函数给 setState(current => { /* play with current */ return current; }) |
14 codehz 2024 年 5 月 10 日 异步请求除了处理中,还有分页(无限滚动),缓存,失败,重试,自动刷新等需求,全部集合到一起,你就重新发明了 swr ,倒不如一开始就直接用 |
15 yukinotech 2024 年 5 月 10 日 @codehz setState(current => { /* play with current */ return current; }) 太艹了 |
16 northquq 2024 年 5 月 11 日 |
17 sh666 2024 年 5 月 11 日 setSubmitting((prev) => { console.log('submitting: ', prev); return prev; }); setSubmitting(false); |
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 |
19 rookie2luochao 2024 年 5 月 11 日 如果你不想用,而你的目标就是更好的控制 loading, react19 提供了一个新的 hook 来同步这种 loading ,叫 useTransition |