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

React如何缓存接口返回值 避免组件重渲染重复调用API

React列表接口重复请求解决方案

首先明确问题根源:很多人误以为给useEffect传入空依赖数组就能让请求只执行一次,实际上这个规则只在组件同一挂载周期内生效。路由跳转到详情页时,列表组件会被完全卸载,内部状态全部清空,返回列表时组件重新挂载,useEffect自然会重新执行触发请求。

以下三种方案可实现接口首次请求后缓存数据,避免重复调用:


方案1:React Context全局缓存(零额外依赖,推荐优先使用)

不需要引入第三方库,利用React自带的Context做跨组件状态共享,把用户列表数据存在全局,组件重挂载时直接读已有缓存:

  • 第一步:创建全局数据Context
// src/context/UserDataContext.jsx
import { createContext, useContext, useState, useCallback } from 'react';

const UserDataContext = createContext(null);

export function UserDataProvider({ children }) {
  const [userList, setUserList] = useState(null);
  const [loading, setLoading] = useState(false);

  const fetchUserList = useCallback(async () => {
    // 已有缓存直接返回,不再发起请求
    if (userList) return;
    setLoading(true);
    try {
      // 替换为你的实际接口请求逻辑
      const res = await fetch('/api/user/list');
      const data = await res.json();
      setUserList(data);
    } catch (err) {
      console.error('用户列表获取失败:', err);
    } finally {
      setLoading(false);
    }
  }, [userList]);

  return (
    <UserDataContext.Provider value={{ userList, loading, fetchUserList }}>
      {children}
    </UserDataContext.Provider>
  );
}

export function useUserData() {
  return useContext(UserDataContext);
}
  • 第二步:在路由最外层包裹Provider,保证路由切换时全局状态不丢失
// src/App.jsx
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import { UserDataProvider } from './context/UserDataContext';
import UserListPage from './pages/UserListPage';
import UserDetailPage from './pages/UserDetailPage';

function App() {
  return (
    <UserDataProvider>
      <BrowserRouter>
        <Routes>
          <Route path="/users" element={<UserListPage />} />
          <Route path="/users/:userId" element={<UserDetailPage />} />
        </Routes>
      </BrowserRouter>
    </UserDataProvider>
  );
}
  • 第三步:改造列表组件,从全局Context取数据和请求方法
// src/pages/UserListPage.jsx
import { useEffect } from 'react';
import { Grid } from '@progress/kendo-react-grid';
import { useNavigate } from 'react-router-dom';
import { useUserData } from '../context/UserDataContext';

export default function UserListPage() {
  const navigate = useNavigate();
  const { userList, loading, fetchUserList } = useUserData();

  useEffect(() => {
    fetchUserList();
  }, [fetchUserList]);

  if (loading) return <div>列表加载中...</div>;

  return (
    <Grid
      data={userList || []}
      onRowClick={(e) => navigate(`/users/${e.dataItem.id}`)}
      // 其余Kendo Grid配置保持和原来一致即可
    />
  );
}

该方案适配所有React路由场景,只要不手动刷新浏览器,后续从详情页返回列表都不会重复发起请求


方案2:本地存储持久化缓存(需刷新页面也不重复请求时使用)

如果需要用户刷新浏览器后也不用重新等待接口响应,可以配合sessionStorage/localStorage做本地持久化缓存,给缓存设置有效期避免数据长期不更新:

// 封装带缓存的请求方法
const USER_LIST_CACHE_KEY = 'USER_LIST_CACHE';
const CACHE_TTL = 30 * 60 * 1000; // 缓存有效期30分钟,可根据业务调整

async function getCachedUserList() {
  // 优先读本地缓存
  const cacheRaw = sessionStorage.getItem(USER_LIST_CACHE_KEY);
  if (cacheRaw) {
    const { data, expireAt } = JSON.parse(cacheRaw);
    // 缓存未过期直接返回
    if (Date.now() < expireAt) return data;
  }
  // 无有效缓存时发起请求
  const res = await fetch('/api/user/list');
  const data = await res.json();
  // 写入新缓存
  sessionStorage.setItem(USER_LIST_CACHE_KEY, JSON.stringify({
    data,
    expireAt: Date.now() + CACHE_TTL
  }));
  return data;
}

可以把这个方法直接替换方案1里的请求逻辑,实现刷新页面也不重复调用接口。


方案3:利用数据请求库自带缓存(项目已集成相关库时使用)

如果项目里已经在用@tanstack/react-query、swr这类成熟的数据请求库,不需要自己写缓存逻辑,直接用库自带的缓存能力即可。以React Query为例,只要给请求设置固定的queryKey,组件重挂载时会默认优先读取缓存,配置合适的staleTime就能避免重复请求:

import { useQuery } from '@tanstack/react-query';

// 列表组件内直接使用
const { data: userList, isLoading } = useQuery({
  queryKey: ['user', 'list'], // 全局唯一的请求key,相同key自动共享缓存
  queryFn: () => fetch('/api/user/list').then(res => res.json()),
  staleTime: 30 * 60 * 1000, // 30分钟内数据判定为新鲜,不重新发起请求
});

避坑提示:不要把请求逻辑直接写在组件函数体顶层,也不要给useEffect依赖数组传入每次渲染都会生成新引用的对象、函数,否则就算不切换路由也会触发重复请求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 12:24:56