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

React useApi Hook在useEffect中使用触发无限循环的解决方案咨询

React useApi Hook在useEffect中使用触发无限循环的解决方案咨询

我来帮你分析下这个问题的根源,然后一步步给出可行的解决方案~

问题根源

你遇到的无限循环,本质是因为每次组件渲染时,useApi Hook都会返回一个全新的对象(里面的request、cancel等方法都是重新创建的)。当你把vacancyApi放进useEffect的依赖数组时,组件每次渲染都会检测到依赖变化,进而触发useEffect,调用request后又会更新组件状态,再次触发渲染,形成循环。

你之前尝试用useCallback包裹request是对的思路,但还需要补充一些细节才能彻底解决问题,下面是完整的优化方案:


第一步:优化useApi Hook内部实现

我们需要用useCallback缓存request和cancel方法,再用useMemo缓存整个返回对象,确保只有当必要的依赖变化时,才更新这些方法和对象的引用。另外还要补上你代码里缺失的cancel函数实现:

import { useState, useRef, useCallback, useMemo } from 'react';
import { AxiosResponse } from 'axios';

function useApi<T>(apiFunc: (...args: Array<any>) => Promise<AxiosResponse<T>>) {
  const [data, setData] = useState<T | null>(null);
  const [error, setError] = useState("");
  const [loading, setLoading] = useState(false);
  const controllerRef = useRef(new AbortController());

  // 用useCallback缓存cancel方法,确保引用稳定
  const cancel = useCallback(() => {
    controllerRef.current.abort();
    // 可选:取消后重置状态
    setLoading(false);
    setError("Request cancelled");
  }, []);

  // 用useCallback缓存request方法,依赖仅为传入的apiFunc
  const request = useCallback(async (...args: Array<any>) => {
    setLoading(true);
    setError("");
    // 每次请求前创建新的AbortController,避免旧的取消信号影响新请求
    controllerRef.current = new AbortController();
    try {
      // 把AbortController的signal传给apiFunc,支持取消请求
      const response = await apiFunc(...args, { signal: controllerRef.current.signal });
      setData(response.data);
    } catch (error: any) {
      // 区分用户主动取消的错误和其他错误,避免误显示取消错误
      if (error.name !== 'AbortError') {
        setError(error.message || 'Unknown Error');
      }
    } finally {
      setLoading(false);
    }
  }, [apiFunc]);

  // 用useMemo缓存整个返回对象,只有当内部状态或回调变化时才更新
  return useMemo(() => ({
    request,
    cancel,
    data,
    error,
    loading
  }), [request, cancel, data, error, loading]);
}

第二步:确保传入useApi的apiFunc引用稳定

如果你的getVacancy函数是定义在组件内部的,每次组件渲染都会创建新的getVacancy实例,这会导致useApi里的request方法重新创建,进而触发useEffect。所以需要用useCallback缓存getVacancy:

const getVacancy = useCallback(async (id: string) => {
  // 这里是你的API请求逻辑,比如:
  return axios.get(`/api/vacancies/${id}`);
}, []); // 空依赖数组确保函数引用稳定

第三步:优化组件中的useEffect

现在你可以安全地在useEffect中依赖vacancyApi.request(而不是整个vacancyApi对象),这样只有当id变化或request方法引用变化时才会触发,同时加上清理函数取消未完成的请求:

const vacancyApi = useApi(getVacancy);

useEffect(() => {
  if (!!id) {
    vacancyApi.request(id);
  }
  // 清理函数:组件卸载或id变化时,取消未完成的请求
  return () => {
    vacancyApi.cancel();
  };
}, [id, vacancyApi.request]); // 仅依赖id和稳定的request方法

为什么这样能解决问题?

  • useCallback让request和cancel的引用只有在依赖变化时才更新,避免每次渲染都生成新方法。
  • useMemo确保useApi返回的对象引用稳定,只有当内部状态或回调变化时才更新。
  • 依赖vacancyApi.request而不是整个对象,进一步缩小了触发useEffect的条件,只有id变化或request真的更新时才会执行。

这样就能实现仅当id变化时调用API,同时严格遵循React的依赖规则,不用再忽略ESLint的提示啦~

备注:内容来源于stack exchange,提问作者Hans Dabi

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.23 12:14:52