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

React中如何让useEffect仅调用一次加载函数且避免无限循环?

问题分析与解决方案

1. 无限循环的原因

你的第二个useEffect依赖了device.selectedType和device.selectedBrand整个对象,React对依赖项的比较是浅引用比较:

  • 如果这两个对象是Mobx observable,或者每次状态更新时会生成新的对象引用(比如setTypes时直接赋值新数组的元素给selectedType),即使对象内容没变,只要引用变化,就会触发useEffect重新执行。
  • 结合你的代码逻辑,核心问题就是用整个对象作为依赖,导致不必要的重复触发,最终引发循环。

2. 让加载函数仅调用一次的正确写法

你的第一个useEffect已经是正确实现——空依赖数组[]会让它在组件挂载时仅执行一次,无需修改。

3. 修复无限循环的修改方案

方案一:依赖对象的具体属性(推荐)

既然我们只关心selectedType和selectedBrand的id变化,直接依赖id而非整个对象,避免引用变化导致的不必要触发:

useEffect(() => {
  // 处理初始时selectedType/selectedBrand可能为空的情况
  const typeId = device.selectedType?.id || null;
  const brandId = device.selectedBrand?.id || null;

  fetchDevices(typeId, brandId, device.page, 5).then(data => { 
    device.setDevices(data.rows);
    device.setTotalCount(data.count);
  })
}, [device.page, device.selectedType?.id, device.selectedBrand?.id]);

方案二:添加竞态条件防护(可选,优化体验)

快速切换分页/类型/品牌时,旧的请求可能晚于新请求返回,导致页面显示错误数据。可以用AbortController取消旧请求:

useEffect(() => {
  const abortController = new AbortController();
  const signal = abortController.signal;

  const typeId = device.selectedType?.id || null;
  const brandId = device.selectedBrand?.id || null;

  // 需要修改fetchDevices函数,支持传入signal参数
  fetchDevices(typeId, brandId, device.page, 5, { signal })
    .then(data => { 
      device.setDevices(data.rows);
      device.setTotalCount(data.count);
    })
    .catch(error => {
      // 忽略取消请求的错误
      if (error.name !== 'AbortError') {
        console.error('获取设备失败:', error);
      }
    });

  // 组件卸载或依赖变化时取消请求
  return () => abortController.abort();
}, [device.page, device.selectedType?.id, device.selectedBrand?.id]);

方案三:使用Mobx的reaction替代useEffect(进阶)

因为你用了Mobx,可以直接用Mobx的reaction响应状态变化,避开React依赖数组的问题:

import { reaction } from 'mobx';

const Shop = observer(() => {
  const { device } = useContext(Context);

  // 组件挂载时执行初始加载+绑定状态监听
  useEffect(() => {
    fetchTypes().then(data => device.setTypes(data));
    fetchBrands().then(data => device.setBrands(data));
    fetchDevices(null, null, 1, 5).then(data => {
      device.setDevices(data.rows);
      device.setTotalCount(data.count);
    });

    // 监听page、selectedType.id、selectedBrand.id的变化
    const dispose = reaction(
      () => [device.page, device.selectedType?.id, device.selectedBrand?.id],
      ([page, typeId, brandId]) => {
        fetchDevices(typeId || null, brandId || null, page, 5).then(data => {
          device.setDevices(data.rows);
          device.setTotalCount(data.count);
        });
      }
    );

    // 组件卸载时清理监听
    return () => dispose();
  }, []);

  // ... 其他组件代码
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 04:45:44