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

React useEffect拼接state触发无限循环的最佳解决方案

解决方案

你遇到的问题本质是对useState更新方式的使用场景不熟悉,根本不需要把list加入effect依赖数组——你在effect里用到list只是为了在更新状态时拿到之前的列表值做拼接,完全没必要因为这个值的变化重新触发请求。

直接用setState的函数式更新语法就能同时满足lint规则和避免无限循环,这也是官方推荐的标准写法:

function Component({ page }) {
  const [list, setList] = useState([]);
  
  useEffect(() => {
    // 补充竞态处理,避免快速切页时旧请求晚返回覆盖新数据
    const abortController = new AbortController();

    axios.get(`${[url]}${page}`, { signal: abortController.signal })
      .then(newList => {
        // 函数式更新可以直接拿到上一次更新的最新state,不需要从外部作用域读取list
        setList(prevList => [...prevList, ...newList]);
      })
      .catch(err => {
        // 忽略主动取消的请求,其余错误正常处理
        if (err.name !== 'CanceledError') console.error(err);
      });

    // 清理逻辑:page变更/组件卸载时取消未完成的旧请求
    return () => abortController.abort();
  }, [page]); // 依赖数组仅保留page,完全符合exhaustive-deps校验规则,无警告
  
  return /* 列表渲染对应的JSX逻辑 */;
}

为什么这个写法符合规范

  • 满足lint校验要求:effect内部没有直接引用外部作用域的list变量,依赖数组只需要包含实际触发effect重跑的page,不会触发任何规则警告
  • 不会产生无限循环:effect的执行仅和page绑定,list更新不会触发effect重跑
  • 状态更新准确:函数式更新入参prevList永远是最新的状态值,不会出现闭包缓存旧值导致列表拼接错误的问题

几个不推荐的反模式写法

  • 不要直接加注释屏蔽exhaustive-deps校验,这种做法会掩盖真实的依赖缺失问题,后续迭代很容易出现闭包导致的旧值bug
  • 不要用useRef存储list绕开依赖检测,这会破坏React单向数据流的一致性,大幅提升后续维护成本
  • 不要把请求逻辑拆到额外的自定义hook里但依然保留list依赖,本质还是没有解决状态更新时的闭包问题

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 14:03:19