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

自定义React Hook useFetch调用时执行8次,求原因排查

排查useFetch多次运行的原因及解决方案

核心问题分析

你的useFetch Hook出现8次请求触发,主要源于以下几个关键问题:

1. useEffect依赖项缺失

当前useEffect的依赖数组是空[],但内部的fetchData函数用到了外部传入的data参数。React会依据依赖数组的变量变化判断是否重新执行useEffect,缺失data依赖会导致:

  • 若data引用变化(比如组件每次渲染都传入新对象,像useFetch({}, {url: '/api/data'})),useEffect无法感知变化,既不能按需重新请求,还可能在组件多次渲染时意外重复执行;
  • 结合React严格模式(开发环境下会让组件挂载两次、useEffect执行两次),再叠加状态更新触发的渲染,请求次数会快速叠加。

2. 传入的data引用不稳定

如果调用useFetch的组件每次渲染都新建data对象(比如直接在组件内写const data = {url: '/api'}),即使useEffect依赖了data,也会因为每次data的引用不同,导致useEffect重复执行,发起多次请求。

3. 组件频繁挂载/渲染

如果调用useFetch的组件因为路由切换、父组件条件渲染等原因反复挂载卸载,或者组件内部有其他状态频繁更新导致多次渲染,都会让useFetch里的useEffect每次挂载/渲染都执行一次。


具体修复步骤

步骤1:修正useEffect的依赖项

把data加入依赖数组,但要处理对象引用变化的问题,两种可选方式:

方式A:拆分data的关键属性作为依赖

如果data里只有url、method这类基础属性,直接把用到的属性单独加入依赖,避免对象引用变化的影响:

useEffect(() => {
  const fetchData = async function () {
    // ... 原fetch逻辑不变
  };

  fetchData();
}, [data.url, data.method, data.headers]); // 只加入实际用到的属性

方式B:使用深度比较的useEffect

如果data结构复杂,自定义一个深度比较的useEffect,确保只有data内容真正变化时才触发请求:

import { useEffect, useRef } from 'react';

// 自定义深度比较工具函数
function isDeepEqual(objA, objB) {
  if (objA === objB) return true;
  if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) return false;
  
  const keysA = Object.keys(objA);
  if (keysA.length !== Object.keys(objB).length) return false;
  
  for (const key of keysA) {
    if (!Object.prototype.hasOwnProperty.call(objB, key) || !isDeepEqual(objA[key], objB[key])) {
      return false;
    }
  }
  return true;
}

// 自定义深度比较useEffect
function useDeepCompareEffect(callback, dependencies) {
  const prevDeps = useRef();
  if (!isDeepEqual(prevDeps.current, dependencies)) {
    prevDeps.current = dependencies;
  }
  useEffect(callback, [prevDeps.current]);
}

// 在useFetch中替换原useEffect
useDeepCompareEffect(() => {
  const fetchData = async function () {
    // ... 原fetch逻辑不变
  };

  fetchData();
}, [data]);

步骤2:确保传入的data引用稳定

在调用useFetch的组件里,用useMemo包裹data,避免每次渲染创建新对象:

import { useMemo } from 'react';
import useFetch from './useFetch';

function MyComponent() {
  const data = useMemo(() => ({
    url: '/api/data',
    method: 'GET'
  }), []); // 依赖空数组,确保data只初始化一次

  const { loadData, isPending, isError } = useFetch({}, data);

  // ... 组件渲染逻辑
}

步骤3:修正Hook里的小笔误

你定义的options参数里IsPending是大写I,和内部的isPending(小写i)不匹配,虽然不影响请求次数,但建议统一:

export const useFetch = function (
  options = {
    isPending: true, // 改为小写i
  },
  data = {}
) {
  // ... 原逻辑不变
};

额外排查点

  • 检查调用useFetch的组件是否被反复挂载卸载(比如路由切换、父组件的条件渲染);
  • 检查组件内部是否有其他状态频繁更新,导致组件多次渲染。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 13:55:11