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

为何基于SWR的自定义React Hook会引发无限循环?

自定义React Hook合并id字段后触发无限循环的原因与解决方法

问题描述

我实现了一个自定义React Hook用于从API获取组织数据,并且需要通过URL参数里的organizationId补充返回数据中缺失的id字段。代码如下:

export const useOrganization = () => { 
  const [data, setData] = useState<OrganizationModel | undefined>(); 
  const { organizationId } = useParams(); 
  const { data: dataSWR } = useSWRImmutable< AxiosResponse<Omit<OrganizationModel, 'id'>> >(`organizations/${organizationId}`, api); 
  useEffect(() => { 
    if (dataSWR?.data && organizationId) { 
      setData({ id: organizationId, ...dataSWR.data }); 
      console.log({ id: organizationId, ...dataSWR.data }); 
    } 
  }); 
  return data; 
};

奇怪的是,当我直接用setData(dataSWR.data)时程序运行正常,但一旦合并id字段写成setData({ id: organizationId, ...dataSWR.data }),Hook就会触发无限循环,想知道这是为什么?


原因分析

你遇到的无限循环问题核心在于**useEffect缺少依赖数组**,并且每次合并生成的都是一个全新的对象引用,导致状态更新后组件重新渲染,进而再次触发useEffect,形成闭环。

具体拆解一下:

  1. 无依赖的useEffect会每次渲染都执行:你的useEffect没有指定第二个参数(依赖数组),这意味着它会在组件的每一次渲染完成后都运行一遍。
  2. 合并操作生成新对象引用:当你执行{ id: organizationId, ...dataSWR.data }时,每次都会创建一个全新的JavaScript对象。即使对象里的属性值完全一样,它的内存引用地址也是不同的。
  3. 状态更新触发重新渲染:调用setData传入新对象后,data状态发生变化,组件(以及Hook所在的上下文)会重新渲染。
  4. 循环闭环形成:重新渲染后,useEffect又会再次执行,检查到dataSWR?.data和organizationId存在,就再次创建新对象并调用setData,如此反复循环。

而当你直接用setData(dataSWR.data)时,dataSWR.data是SWR缓存的同一个对象引用(只要API返回的数据没变化),所以调用setData时,React会对比新旧状态的引用,发现引用相同就不会触发组件重新渲染,自然不会导致循环。


解决方法

有两种简单的方式可以解决这个问题:

方法一:给useEffect添加正确的依赖数组

确保useEffect只在dataSWR.data或organizationId发生变化时才执行:

export const useOrganization = () => { 
  const [data, setData] = useState<OrganizationModel | undefined>(); 
  const { organizationId } = useParams(); 
  const { data: dataSWR } = useSWRImmutable< AxiosResponse<Omit<OrganizationModel, 'id'>> >(`organizations/${organizationId}`, api); 
  useEffect(() => { 
    if (dataSWR?.data && organizationId) { 
      setData({ id: organizationId, ...dataSWR.data }); 
    } 
  }, [dataSWR?.data, organizationId]); // 添加依赖数组
  return data; 
};

这样只有当dataSWR.data或者organizationId的引用/值发生变化时,useEffect才会执行,避免了不必要的重复触发。

方法二:直接在返回时合并id,无需额外状态

其实你完全可以省去useState和useEffect的环节,直接在Hook返回的时候合并id字段,这样更简洁高效:

export const useOrganization = () => { 
  const { organizationId } = useParams(); 
  const { data: dataSWR } = useSWRImmutable< AxiosResponse<Omit<OrganizationModel, 'id'>> >(`organizations/${organizationId}`, api); 
  
  if (!dataSWR?.data || !organizationId) return undefined;
  return { id: organizationId, ...dataSWR.data } as OrganizationModel; 
};

这种方式不需要维护额外的本地状态,直接基于SWR返回的数据进行转换,既避免了循环问题,也简化了代码逻辑。

内容的提问来源于stack exchange,提问作者Adam Zálešák

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.01 00:47:39