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

React异步setState未生效:合并API数据到info状态失败

React setState未正确合并状态且useEffect未触发问题分析

问题背景

我有一个app.jsx文件,会请求API_A与API_B两个不同接口,将获取到的data_A和data_B合并到info状态中用于UI渲染。控制台输出显示监听info的useEffect未触发,data_B未成功合并到info状态中,看起来setState已执行但未生效,React版本为17.0.48。

代码示例

const [info, setInfo] = useState();

function go(){
  API_A().then(({ data }) => {
    setInfo((prev) => {
      const t = {
        ...prev,
        ...data,
      };
      console.log('===== set API_A =====', t);
      // 深克隆,实际场景中返回的是对象
      return JSON.parse(JSON.stringify(t));
    });
  });
  API_B().then(({ data: res = {} }) => {
    setInfo(prev => {
      
      const t = {
        ...prev,
        ...res,
      };
      console.log('===== set API_B =====', t);
      // 深克隆,实际场景中返回的是对象
      return JSON.parse(JSON.stringify(t));
    });
  });
}

useEffect(() => {
  go();
}, [])

useEffect(() => {
  console.log('=====useEffect info', info); 
}, [info])

原因解析

  1. 异步回调的状态竞态问题
    两个API请求并行发起,它们的then回调异步执行。在React 17中,异步回调内的setState默认会被批量处理,但两个回调获取到的prev状态都是初始值undefined(useState未设置初始值)。当两个回调几乎同时执行时,后执行的setState会基于初始状态合并数据,直接覆盖前一个setState的结果,导致其中一个接口的数据丢失。

  2. 状态浅比较导致的渲染跳过
    React会对setState前后的状态进行浅比较,如果两次更新后的状态对象在浅比较下相等(比如两次合并后的对象结构/内容完全一致),React会跳过重渲染,因此监听info的useEffect不会触发。另外,初始状态为undefined,合并后的对象如果和某一次单独更新的对象一致,也会导致后续更新不触发渲染。

解决方案

方案1:合并API请求,一次性更新状态(推荐)

通过Promise.all等待两个接口请求完成后,一次性合并数据并更新状态,彻底避免竞态问题:

// 初始化为空对象,避免合并时出现undefined的问题
const [info, setInfo] = useState({});

function go(){
  Promise.all([API_A(), API_B()])
    .then(([resA, resB]) => {
      const mergedData = {
        ...resA.data,
        ...(resB.data || {})
      };
      // 保持原有深克隆逻辑
      setInfo(JSON.parse(JSON.stringify(mergedData)));
    })
    .catch(err => {
      // 处理请求错误
      console.error('请求失败:', err);
    });
}

方案2:确保状态更新基于最新值(适用于必须单独请求的场景)

如果需要分别处理两个接口的返回结果,可以在更新时确保每次都基于最新的状态,但仍需注意竞态:

const [info, setInfo] = useState({});

function go(){
  API_A().then(({ data }) => {
    setInfo(prev => {
      const updated = { ...prev, ...data };
      return JSON.parse(JSON.stringify(updated));
    });
  });
  
  API_B().then(({ data: res = {} }) => {
    setInfo(prev => {
      const updated = { ...prev, ...res };
      return JSON.parse(JSON.stringify(updated));
    });
  });
}

注:这种方式仍可能因请求响应顺序导致状态覆盖,但比原代码更可靠,因为每次更新都基于当前最新的prev状态。如果接口响应差异较大,建议还是使用方案1。

额外优化

  • 初始状态设置为空对象useState({}),避免合并...prev时因为undefined导致的异常。
  • 若不需要深克隆,可直接返回合并后的对象,减少不必要的性能开销。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 21:07:47