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

如何调整React自定义hook让useEffect内API请求仅执行一次

问题根因
  • 依赖数组配置错误:你当前的useEffect依赖包含了data和fetchFunction两个不稳定变量:
    • 请求成功后setData会触发data更新,直接导致useEffect二次执行
    • 若传入的getPercentageRead、getNotes未做引用稳定处理,每次组件渲染都会生成新的函数引用,同样会触发useEffect重新执行
  • 请求触发逻辑冗余:用data === null判断是否发起请求的逻辑,和不稳定依赖叠加后,很容易重复触发请求
  • (可选)React18+ 开发环境严格模式默认会执行两次useEffect用于检测副作用泄漏,也会导致开发环境下请求重复,生产环境会自动恢复正常。
修复方案

第一步:修改自定义hook useFetchMongoField

import { useState, useEffect, useRef } from 'react';

const useFetchMongoField = (user, id, fetchFunction) => {
  const [hasFetched, setHasFetched] = useState(false);
  const [data, setData] = useState(null);
  const [error, setError] = useState(null);
  // 用于标记请求是否已经发起,不受重渲染影响,避免重复请求
  const hasRequestedRef = useRef(false);

  useEffect(() => {
    // 无有效用户、已经发起过请求、已经拉取到数据,都直接返回
    if (!user || hasRequestedRef.current || hasFetched) return;

    const abortController = new AbortController();
    const fetchData = async () => {
      hasRequestedRef.current = true;
      try {
        let result = await fetchFunction(user.email, id, abortController.signal);
        // 组件未卸载才更新状态
        if (!abortController.signal.aborted) {
          setData(result);
          setHasFetched(true);
        }
      } catch (error) {
        if (!abortController.signal.aborted) {
          setError(error.message);
        }
      }
    };

    fetchData();

    // 组件卸载时中止请求,避免内存泄漏
    return () => abortController.abort();
  // 仅保留必要的稳定依赖
  }, [user, id, fetchFunction, hasFetched]);

  return { data, hasFetched, error };
};

修改说明:

  • 新增hasRequestedRef引用标记请求状态,彻底避免同一hook实例重复发起请求
  • 移除依赖数组中的data,避免请求成功后触发副作用重新执行
  • 新增AbortController处理请求中止,避免组件卸载后仍执行状态更新的报错
  • 用hasFetched和ref双重判断请求触发条件,逻辑更严谨

第二步:稳定传入的请求函数引用

如果getPercentageRead、getNotes是在组件内部定义的,需要用useCallback包裹函数,保证依赖不变的情况下函数引用不会刷新,示例:

const getPercentageRead = useCallback(async (email, isbn, signal) => {
  // 原有请求逻辑,可传入signal用于中止请求
  const res = await fetch(`/api/read-percentage?email=${email}&isbn=${isbn}`, { signal });
  return res.json();
}, []);

const getNotes = useCallback(async (email, isbn, signal) => {
  // 原有请求逻辑
  const res = await fetch(`/api/notes?email=${email}&isbn=${isbn}`, { signal });
  return res.json();
}, []);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.09 15:45:03