自定义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
相关产品推荐
相关产品推荐

