You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Hooks中useState异步特性相关问题咨询

关于React useState异步更新与控制台输出顺序的疑问

我运行以下React代码时遇到了执行顺序的困惑:

import {useState, useEffect} from 'react'

function App() {
  const [data, setData] = useState([]);

  useEffect(()=>{
    console.log("useEffect start")                     // Step 1 after useEffect start
    const getApiData = async () => {
      console.log("async function start")                // Step 2
      const apiData = await fetch("https://api.chucknorris.io/jokes/random").then(res=>res.json());
      console.log("get API data")                        // Step 3
      setData(data.concat(apiData));
      console.log("after setData", data)}                 // Step 6
    getApiData();
  },[])
  console.log("outside useEffect", data);                 // Step 4

  return (<>
     <li>{data.map(i => <li>{i.value}</li>)}</li>
     {console.log("inside return")}                      // Step 5
  </>)} 

export default App

控制台输出顺序如下:

outside useEffect []
inside return
useEffect start
async function start
get API data
outside useEffect [{…}]  // 既然useState的setData是异步的,为什么这一步能在setData完成前获取到更新后的data?
inside return
after setData []

我的疑问是:既然useState的setData是异步的,为什么能在上述标记的步骤中获取到更新后的data?这似乎意味着setData在该步骤前已经完成,但为什么setData之后的console.log("after setData", data)反而在这一步之后执行?而且该日志输出的还是旧的data值。我使用的是React v18,且通过ReactDOM.render启动应用。


核心原因:闭包捕获 + React状态更新的调度逻辑

1. setData的"异步"不是你理解的Promise异步

React里useState的setter(setData)的异步,指的是它不会立即修改当前函数闭包中的变量,但它会立刻通知React:组件需要更新状态并重新渲染。

你在async函数里的data变量,是组件第一次渲染时被闭包捕获的初始值(空数组)。不管后续状态怎么更新,这个async函数里的data永远都是第一次渲染时的那个空数组,所以最后console.log("after setData", data)输出的是旧值。

2. 重渲染会优先于async函数的剩余代码执行

当你调用setData(data.concat(apiData))后,React会立刻调度组件的重渲染任务。而await之后的代码(包括console.log("after setData", data))会被放到微任务队列中等待执行。

在React 18的开发模式下,为了让开发者能更快看到状态更新的结果,React会优先执行重渲染任务,而非等待微任务队列。所以组件会先完成重新渲染:

  • 执行console.log("outside useEffect", data),这里的data是更新后的新数组(属于新的渲染周期,变量是全新的)
  • 然后执行return里的console.log("inside return")

等重渲染完全完成后,才会回到微任务队列,执行async函数里剩下的console.log("after setData", data),这时候用的还是闭包里的旧data。

3. 补充:如何在async函数里获取更新后的状态

如果想在async函数里拿到最新的状态,你可以使用useState的函数式更新,它能接收上一次的状态值:

setData(prevData => prevData.concat(apiData));

这种写法不需要依赖当前闭包的data值,直接用React传入的最新状态进行更新。

内容的提问来源于stack exchange,提问作者Mark

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 06:55:56