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

React中如何避免列表子组件EventCard重复调用API

解决EventCard重复调用API的问题

首先,你当前的useEffect存在一个小问题:定义了异步函数但没有执行,需要补充调用逻辑,但这不是重复请求的核心原因。核心问题是每个EventCard实例都会独立发起fetchZones请求,导致重复调用。以下是几种符合你需求的解决方案:

1. 给fetchZones添加单例缓存(最直接的方案)

用闭包封装带缓存的请求函数,相同serverId下只会发起一次请求,后续调用直接复用之前的Promise结果:

// 封装带缓存的fetchZones函数
const fetchZonesWithCache = (() => {
  // 用Map缓存每个serverId对应的请求Promise
  const requestCache = new Map<string, Promise<Zone[]>>();
  
  return async (serverId: string) => {
    // 如果已有缓存,直接返回缓存的Promise
    if (requestCache.has(serverId)) {
      return requestCache.get(serverId)!;
    }
    
    // 没有缓存则发起请求
    const fetchPromise = fetch(`/api/zones?serverId=${serverId}`)
      .then(res => {
        if (!res.ok) throw new Error('请求失败');
        return res.json();
      })
      .catch(err => {
        // 请求失败时删除缓存,允许后续重试
        requestCache.delete(serverId);
        throw err;
      });
    
    requestCache.set(serverId, fetchPromise);
    return fetchPromise;
  };
})();

// 在EventCard中使用
useEffect(() => {
  const loadZones = async () => {
    try {
      const zones = await fetchZonesWithCache(serverId);
      setZones(zones);
    } catch (err) {
      // 这里处理错误,比如提示用户
      console.error('加载时区失败:', err);
    }
  };
  
  loadZones();
}, [serverId]);

2. 封装自定义Hook复用缓存逻辑

如果EventCard在多个地方使用,把缓存和状态管理封装成自定义Hook,让逻辑更清晰:

import { useState, useEffect } from 'react';

// 复用带缓存的fetch函数
const fetchZonesWithCache = (() => {
  const requestCache = new Map<string, Promise<Zone[]>>();
  
  return async (serverId: string) => {
    if (requestCache.has(serverId)) {
      return requestCache.get(serverId)!;
    }
    
    const fetchPromise = fetch(`/api/zones?serverId=${serverId}`)
      .then(res => {
        if (!res.ok) throw new Error('请求失败');
        return res.json();
      })
      .catch(err => {
        requestCache.delete(serverId);
        throw err;
      });
    
    requestCache.set(serverId, fetchPromise);
    return fetchPromise;
  };
})();

// 自定义Hook
function useZones(serverId: string) {
  const [zones, setZones] = useState<Zone[] | null>(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<Error | null>(null);

  useEffect(() => {
    let isMounted = true; // 防止组件卸载后更新状态
    
    const loadZones = async () => {
      try {
        const data = await fetchZonesWithCache(serverId);
        if (isMounted) {
          setZones(data);
          setLoading(false);
        }
      } catch (err) {
        if (isMounted) {
          setError(err as Error);
          setLoading(false);
        }
      }
    };
    
    loadZones();
    return () => { isMounted = false; };
  }, [serverId]);

  return { zones, loading, error };
}

// 在EventCard中使用
const { zones, loading, error } = useZones(serverId);

// 渲染时可以根据loading/error状态处理
if (loading) return <div>加载中...</div>;
if (error) return <div>加载失败:{error.message}</div>;

为什么useMemo/useCallback没解决问题?

useMemo和useCallback是用来缓存组件渲染结果或函数引用,避免不必要的重渲染,但它们无法控制API请求的重复发起——每个EventCard实例都是独立的,就算引用被缓存,每个实例还是会各自执行useEffect里的请求逻辑。核心解决方案是对请求本身做缓存,而不是缓存组件或函数。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 09:27:06