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

React useEffect依赖数组触发时state为初始0的问题排查与修复

React useEffect 依赖数组偶现读取到初始状态值问题

问题根因

  • 状态独立更新触发的时序竞态:value1、value2是两个独立的state,接口返回后顺序调用两次setState。在React17及更早版本中,异步请求回调不属于React受控事件上下文,不会自动触发更新批处理,两次setState会触发两次独立的组件重渲染、两次依赖项为[value1, value2]的useEffect执行。第二次重渲染调度时如果存在优先级插队,effect闭包可能捕获到首次渲染的快照值,就会出现value1为初始值0的异常。
  • 多余的effect层带来的不可控性:原本接口返回时已经拿到了最新的value1、value2,却非要绕一层监听state变化的effect来执行后续逻辑,多了一次渲染调度流程,放大了闭包、时序问题出现的概率。
  • 代码存在显性笔误:初始化空依赖的useEffect中调用的是getApiValues(),但实际定义的接口请求函数名为getApiValue1;接口成功回调里的setvalue1(value1)、setvalue2(value2)也存在拼写、取值错误,会直接把闭包中的初始值设置回state。

修复方案

优先选择最简洁、最不容易出问题的实现:去掉多余的state监听effect,直接在接口请求成功拿到返回值后,同步更新state并执行后续逻辑,从根源避开依赖数组、闭包、时序问题。

const [value1, setValue1] = useState(0);
const [value2, setValue2] = useState(0);

// 页面初始化请求
useEffect(() => {
  // 把请求函数定义在effect内部,避免不必要的依赖声明
  const fetchApiData = async () => {
    const info = {
      corpid: 'corpid',     
      userid: 'userid'
    };
    try {
      const res = await fetch(`url`,{
        method: "POST",
        headers: {
          "Accept": "application/json",
          "Content-Type": "application/json"
        },
        credentials: 'include',
        body: JSON.stringify(info)
      });
      const json = await res.json();
      if(json.result === 'succ') {
        // 替换为实际接口返回的字段
        const newVal1 = json.data.value1;
        const newVal2 = json.data.value2;
        // 更新状态
        setValue1(newVal1);
        setValue2(newVal2);
        // 直接用最新返回值执行后续逻辑,不需要等state更新触发effect
        setApiValues(newVal1, newVal2);
      }
    } catch(err) {
      // 自行处理请求错误
      console.error(err);
    }
  };

  fetchApiData();
// 空依赖,仅在组件挂载时执行一次
}, []);

const setApiValues = (val1, val2) => {
  // 原有业务逻辑
}

如果业务场景确实需要保留监听value1、value2变化的effect,就把两个强关联的状态合并为单个state对象,保证接口返回时仅触发一次setState、一次重渲染、一次effect执行,避免多次更新带来的竞态:

// 合并关联状态
const [apiValues, setApiValues] = useState({ value1: 0, value2: 0 });

useEffect(() => {
  const fetchApiData = async () => {
    // ...省略请求逻辑,和上面一致
    if(json.result === 'succ') {
      // 单次更新,仅触发一次重渲染
      setApiValues({
        value1: json.data.value1,
        value2: json.data.value2
      })
    }
  };
  fetchApiData();
}, []);

useEffect(() => {
  // 状态合并后只会在两个值都更新完成后触发一次
  setApiValues(apiValues.value1, apiValues.value2);
}, [apiValues]);

额外注意事项

  • React18已经默认对所有上下文的更新做自动批处理,上述独立setState多次触发重渲染的问题会大幅减少,但依然不建议靠监听state变化来执行接口返回后的后续逻辑,多余的调度层只会增加问题出现的概率。
  • 异步请求需要增加组件卸载判断,比如用AbortController中断请求、或者用挂载标记位判断,避免组件卸载后依然调用setState引发内存泄漏。
  • useEffect依赖数组一定要包含所有在effect内部使用的外部变量,不要为了规避eslint警告随意写空依赖或者删依赖项,否则很容易出现闭包捕获陈旧值的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 03:51:23