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

