React组件重新创建时如何持久化状态?解决集群重复请求问题
问题描述
我开发了一个React应用,从数据库获取集群列表并以网格视图展示。由于集群列表极少更新(约每周一次),我不希望用户每次访问/clusters路径时都重新获取数据。我尝试把useState换成useRef,但两种方式下,当组件卸载后重新挂载(你说的“重新创建”对应的标准术语是这个)时,clusters的值都会回到[]。请问React的标准解决方案是什么?
补充说明:你提到的useRef确实会返回一个在组件生命周期内保持不变的可变对象,但它的作用范围仅限于组件的单次挂载周期——当组件被卸载后,这个ref对象会被销毁,重新挂载时会初始化新的ref实例,所以还是会回到初始值。
React标准解决方案
针对这种低频更新数据的缓存需求,有几种常用的标准处理方式:
1. 组件外部变量缓存
在组件文件的外部定义缓存变量,存储已获取的集群数据和过期时间,组件挂载时先检查缓存是否有效,无效再发起请求。这种方式简单直接,适合单个组件复用缓存的场景。
修改ClusterList.jsx示例:
// 组件外部定义全局缓存变量,不受组件挂载/卸载影响 let cachedClusters = []; let cacheExpiry = 0; // 缓存过期时间戳(毫秒) const CACHE_DURATION = 7 * 24 * 60 * 60 * 1000; // 一周有效期 export const ClusterList = () => { const discoveryApi = useApi(discoveryApiRef); const authFetch = useAuthFetch(); const [loading, setLoading] = useState<boolean>(false); const [clusters, setClusters] = useState<Cluster[]>([]); const fetchClusters = useCallback(async (): Promise<Cluster[]> => { const url = await discoveryApi.getBaseUrl('containerplatform'); const response = await authFetch(`${url}/clusters`); return await response.json(); }, [discoveryApi, authFetch]); useEffect(() => { (async () => { try { const now = Date.now(); // 检查缓存是否存在且未过期 if (cachedClusters.length > 0 && now < cacheExpiry) { setClusters(cachedClusters); return; } setLoading(true); const _clusters = await fetchClusters(); setClusters(_clusters); // 更新缓存和过期时间 cachedClusters = _clusters; cacheExpiry = now + CACHE_DURATION; } finally { setLoading(false); } })(); }, [fetchClusters]); // ... 其余网格渲染代码 };
2. React Context全局缓存
如果多个组件(比如ClusterInfo)也需要用到集群数据,可以用Context把缓存逻辑抽离出来,实现全局复用。
第一步:创建ClusterContext
// ClusterContext.jsx import { createContext, useContext, useState, useEffect } from 'react'; const ClusterContext = createContext({ clusters: [], loading: false, }); export const ClusterProvider = ({ children }) => { const [clusters, setClusters] = useState<Cluster[]>([]); const [loading, setLoading] = useState(false); const discoveryApi = useApi(discoveryApiRef); const authFetch = useAuthFetch(); const fetchClusters = async () => { const url = await discoveryApi.getBaseUrl('containerplatform'); const response = await authFetch(`${url}/clusters`); return response.json(); }; useEffect(() => { const checkCache = async () => { // 结合localStorage实现页面刷新后仍保留缓存 const cached = localStorage.getItem('cachedClusters'); const expiry = localStorage.getItem('clusterCacheExpiry'); const now = Date.now(); if (cached && expiry && now < Number(expiry)) { setClusters(JSON.parse(cached)); return; } setLoading(true); try { const data = await fetchClusters(); setClusters(data); localStorage.setItem('cachedClusters', JSON.stringify(data)); localStorage.setItem('clusterCacheExpiry', String(now + CACHE_DURATION)); } finally { setLoading(false); } }; checkCache(); }, [discoveryApi, authFetch]); return ( <ClusterContext.Provider value={{ clusters, loading }}> {children} </ClusterContext.Provider> ); }; export const useClusterContext = () => useContext(ClusterContext);
第二步:在App中包裹Provider
修改App.jsx:
import { ClusterProvider } from './ClusterContext'; function App() { return ( <ClusterProvider> <Routes> <Route path="/" element={<Home />} /> <Route path="/clusters" element={<ClusterList />} /> <Route path="/clusters/:id" element={<ClusterInfo />} /> <Route path="/assets" element={<Assets />} /> </Routes> </ClusterProvider> ); }
第三步:在组件中使用Context
// ClusterList.jsx export const ClusterList = () => { const { clusters, loading } = useClusterContext(); // 直接使用缓存好的数据,无需自己处理请求逻辑 // ... 网格渲染代码 };
3. 本地存储持久化
如果希望页面刷新后缓存依然有效,可以直接把数据存在localStorage或sessionStorage,同时记录过期时间:
修改ClusterList.jsx的useEffect部分:
useEffect(() => { (async () => { try { const now = Date.now(); const cachedClusters = localStorage.getItem('clusters'); const cacheExpiry = localStorage.getItem('clusterCacheExpiry'); if (cachedClusters && cacheExpiry && now < Number(cacheExpiry)) { setClusters(JSON.parse(cachedClusters)); return; } setLoading(true); const _clusters = await fetchClusters(); setClusters(_clusters); localStorage.setItem('clusters', JSON.stringify(_clusters)); localStorage.setItem('clusterCacheExpiry', String(now + 7*24*60*60*1000)); } finally { setLoading(false); } })(); }, [fetchClusters]);
内容的提问来源于stack exchange,提问作者Shuzheng
相关产品推荐
相关产品推荐

