HHQKK 2024-04-16 13:56 采纳率: 0%
浏览 6

useState在异步函数中没有正确的更新值

useState在异步函数中没有正确的时机更新值,试了网上查询的方法,比如使用useEffect监控isLoading的值,或者是setIsLoading使用回调方法,但是都没有用,从控制台上看的话似乎是什么东西堵塞了setIsLoading的执行,因为execute被正确执行了,并且resule的结果也是正确的,而isLoading的更新是在execute函数执行完后才更新为true的。自己琢磨了很久,依然无法解决

const [isLoading, setIsLoading] = useState<boolean>(false);
const execute = useCallback(
    async (input: TInput) => {
      // 设置加载状态
      setIsLoading((prev) => {
        console.log("isLoading changed:", prev);
        return true;
      });

      try {
        // 执行操作并获取结果

        const result = await action(input);

        // 判断结果是否为空
        if (!result) {
          return;
        }

        // 更新字段错误信息
        setFieldErrors(result.fieldErrors);

        // 判断是否有错误信息
        if (result.error) {
          setError(result.error);
          options.onError?.(result.error);
        }

        // 更新数据信息
        if (result.data) {
          setData(result.data);
          options.onSuccess?.(result.data);
        }
      } finally {
        // 设置加载状态为false
        setIsLoading((prev) => {
          console.log("isLoading changed:", prev);
          return false;
        });
        options.OnComplete?.();
      }
    },
    [action, options]
  );

  • 写回答

4条回答 默认 最新

  • 专家-赤兔[在线] 全栈领域优质创作者 2024-04-16 13:56
    关注

    引自免费微信小程序:皆我百晓生

    看起来你在处理异步操作时遇到了React Hooks中的一个常见问题。当在异步操作中更新state时,由于React state更新是异步的,因此可能会导致你在同一渲染周期内观察到旧的state值。为了解决这个问题,可以尝试在execute函数内部使用Promise.all确保所有的异步操作完成后再设置isLoading为false。这里是一个修改后的版本:

    const execute = useCallback(async (input: TInput) => {
      setIsLoading(true);
    
      try {
        // 执行操作并获取结果
        const result = await action(input);
    
        // ...
      } catch (error) {
        // ...
      } finally {
        Promise.all([
          // 确保所有异步操作(如onError、onSuccess等)完成
          ...options.onError ? [new Promise((resolve) => setTimeout(resolve, 0))] : [],
          ...options.onSuccess ? [new Promise((resolve) => setTimeout(resolve, 0))] : [],
          ...options.OnComplete ? [new Promise((resolve) => setTimeout(resolve, 0))] : []
        ]).then(() => {
          setIsLoading(false);
        });
      }
    }, [action, options]);
    

    这个改动主要是通过创建空的Promise并在稍后解析它们,这样可以确保这些Promise在当前渲染周期结束之后才会解析。这将允许在下一个渲染周期开始之前更新isLoading状态。

    此外,考虑到你提到的console.log可能显示旧的isLoading值,这是因为在React中,state更新可能不会立即反映在渲染中。如果你想要在控制台看到最新的state值,可以在组件的render方法中打印它,而不是在setIsLoading的回调中打印。例如,在你的组件中添加这样的逻辑:

    return (
      <div>
        {/* ... */}
        {isLoading && <p>Loading...</p>}
        {/* ... */}
      </div>
      // 在返回元素的最后部分
      <pre>{JSON.stringify({ isLoading }, null, 2)}</pre> 
    );
    

    这将在页面上显示一个"Loading..."提示以及当前的isLoading状态,方便你进行调试。

    评论 编辑记录

报告相同问题?

问题事件

  • 修改了问题 4月16日
  • 创建了问题 4月16日

悬赏问题

  • ¥15 35114 SVAC视频验签的问题
  • ¥15 impedancepy
  • ¥15 在虚拟机环境下完成以下,要求截图!
  • ¥15 求往届大挑得奖作品(ppt…)
  • ¥15 如何在vue.config.js中读取到public文件夹下window.APP_CONFIG.API_BASE_URL的值
  • ¥50 浦育平台scratch图形化编程
  • ¥20 求这个的原理图 只要原理图
  • ¥15 vue2项目中,如何配置环境,可以在打完包之后修改请求的服务器地址
  • ¥20 微信的店铺小程序如何修改背景图
  • ¥15 UE5.1局部变量对蓝图不可见