首页 > 解决方案 > useState 在异步之前加载不更新

问题描述

我有一个函数 runValidation 遍历一堆数字并进行一些计算。这可能需要一段时间,所以我想包含一个“isLoading”useState。

所以我做了一个简单的

const [isLoading, setIsLoading] = useState(false)

当我单击一个按钮时,我想将 isLoading 设置为 true,运行我的函数,然后执行 setIsLoading(false)。所以我使我的验证功能异步,以及按钮处理程序

const handleClick = async () => {
    setIsLoading(true)
    const isValid = await runValidation()
    setIsLoading(false)
}

我的按钮组件很简单

<button onClick={() => handleClick()}>Run Validations</button>

但是,每次我尝试运行它时,在验证功能完成之前都不会设置加载。因此,在“isLoading”变量上使用 useEffect。我得到 console.logs 显示

Running Validations
isLoading: true
isLoading: false

为什么我的“isLoading”在异步之后才为真?有没有办法确定?我尝试将 setIsLoading(true) 向下移动到 onClick 按钮 - 我尝试使用 .then() 而不是 await 等 - 但 isLoading 仅在异步函数之后设置为 true。

编辑:示例 - https://codesandbox.io/s/suspicious-cartwright-0y5jk

标签: reactjsasynchronoususe-state

解决方案


仅仅因为你声明了一个函数async并不意味着它实际上是一个异步函数,它只是允许你await函数中使用关键字并隐式返回一个 Promise。

在您的示例代码框的情况下,runValidation已声明async但运行完全同步的代码。

const runValidation = async () => {
  let result = 0;
  console.log("Running Validation");
  for (let i = 1; i <= 500000; i++) {
    if (i > 200000) {
      result = i;
      break;
    }
  }

  return result;
};

如果您实际上是异步执行,那么您会注意到控制台日志是相同的。

例子:

const runValidation = async () => {
  console.log("Running Validation");

  const result = await new Promise((resolve) => {
    setTimeout(() => {
      let result = 0;
      for (let i = 1; i <= 500000; i++) {
        if (i > 200000) {
          result = i;
          break;
        }
      }
      resolve(result);
    }, 3000);
  });

  return result;
};
Running Validation // logged immediately in callback
isLoading true     // logged in useEffect next render cycle
isLoading false    // logged in useEffect ~3 seconds later

编辑 usestate-loading-not-updating-before-async

为什么日志输出一样?

const handleClick = async () => {
  setIsLoading(true);
  await runValidation();
  setIsLoading(false);
};

在这里,您已将状态更新排入队列,然后立即调用runValidation,这将在控制台记录"Running Validation",然后"isLoading true"useEffect. 等待隐式 Promise(时间不长),然后将另一个状态更新排入队列并"isLoading false"useEffect.

您的代码似乎完全按照我的预期工作。

如果您将验证日志移动异步逻辑中,那么控制台日志输出可能更像您的预期,因为您现在让 React 状态有机会在“异步”代码完成“触发”之前更新和重新呈现。

const runValidation = async () => {
  const result = await new Promise((resolve) => {
    setTimeout(() => {
      console.log("Running Validation");
      let result = 0;
      for (let i = 1; i <= 500000; i++) {
        if (i > 200000) {
          result = i;
          break;
        }
      }
      resolve(result);
    }, 3000);
  });

  return result;
};

输出

isLoading true
Running Validation 
isLoading false

推荐阅读