如何在React组件挂载与卸载间持久化API获取的国家数据
可行实现方案汇总
针对你需要持久化API数据、避免重复加载的需求,以下是几种实用方案,按复杂度和适用场景排序:
1. 模块级全局缓存(最简方案)
利用JS模块的单例特性,在单独文件中维护缓存状态,整个应用生命周期内(页面不刷新)都会保留数据,完美覆盖组件卸载重挂载的场景。
实现代码
// countryCache.js let countryData = null; let isLoading = false; let pendingResolves = []; export async function fetchCountries() { // 已有缓存直接返回 if (countryData) return countryData; // 处理并发请求,避免重复调用API if (isLoading) { return new Promise(resolve => pendingResolves.push(resolve)); } isLoading = true; try { const res = await fetch('你的国家数据API地址'); countryData = await res.json(); // 通知所有等待中的请求 pendingResolves.forEach(resolve => resolve(countryData)); pendingResolves = []; return countryData; } catch (err) { pendingResolves.forEach(resolve => resolve(Promise.reject(err))); pendingResolves = []; throw err; } finally { isLoading = false; } }
组件中使用
import { useState, useEffect } from 'react'; import { fetchCountries } from './countryCache'; function CountrySelector() { const [countries, setCountries] = useState(null); const [loading, setLoading] = useState(false); const [error, setError] = useState(null); useEffect(() => { let isMounted = true; const loadData = async () => { try { setLoading(true); const data = await fetchCountries(); if (isMounted) setCountries(data); } catch (err) { if (isMounted) setError(err); } finally { if (isMounted) setLoading(false); } }; if (!countries) loadData(); return () => isMounted = false; }, [countries]); if (loading) return <div>加载国家数据中...</div>; if (error) return <div>加载失败:{error.message}</div>; if (!countries) return null; return ( <select> {countries.map(c => ( <option key={c.iso} value={c.iso}> {c.flag} {c.name} </option> ))} </select> ); }
优缺点
- ✅ 无额外依赖,实现简单
- ✅ 跨组件共享,卸载重挂载直接复用缓存
- ❌ 页面刷新后缓存丢失
2. 全局缓存 + localStorage(持久化到本地)
在方案1的基础上,将缓存数据存入localStorage,实现页面刷新后仍保留数据。
修改缓存文件
// countryCache.js const STORAGE_KEY = 'country_data_cache'; let countryData = null; let isLoading = false; let pendingResolves = []; // 初始化时读取本地缓存 function loadFromLocalStorage() { const stored = localStorage.getItem(STORAGE_KEY); if (stored) { try { countryData = JSON.parse(stored); } catch (e) { localStorage.removeItem(STORAGE_KEY); } } } loadFromLocalStorage(); export async function fetchCountries() { if (countryData) return countryData; if (isLoading) { return new Promise(resolve => pendingResolves.push(resolve)); } isLoading = true; try { const res = await fetch('你的国家数据API地址'); countryData = await res.json(); // 写入本地存储 localStorage.setItem(STORAGE_KEY, JSON.stringify(countryData)); pendingResolves.forEach(resolve => resolve(countryData)); pendingResolves = []; return countryData; } catch (err) { pendingResolves.forEach(resolve => resolve(Promise.reject(err))); pendingResolves = []; throw err; } finally { isLoading = false; } } // 可选:手动清空缓存的方法 export function clearCountryCache() { countryData = null; localStorage.removeItem(STORAGE_KEY); }
优缺点
- ✅ 页面刷新后仍保留数据
- ✅ 兼容方案1的所有优点
- ❌ 存储容量有限(localStorage约5MB)
- ❌ API数据更新时需手动清空缓存或添加过期逻辑
3. React Context + 全局缓存(多组件共享场景)
如果多个组件需要使用国家数据,可以用Context封装状态,结合全局缓存实现跨组件共享和持久化。
实现代码
// CountryContext.js import { createContext, useContext, useReducer, useEffect } from 'react'; const CountryContext = createContext(); const initialState = { countries: null, loading: false, error: null }; // 全局缓存 let globalCache = null; let isLoadingGlobal = false; function reducer(state, action) { switch (action.type) { case 'LOAD_START': return {...state, loading: true, error: null}; case 'LOAD_SUCCESS': return {...state, loading: false, countries: action.payload}; case 'LOAD_ERROR': return {...state, loading: false, error: action.payload}; default: throw new Error(`未知动作:${action.type}`); } } export function CountryProvider({ children }) { const [state, dispatch] = useReducer(reducer, initialState); useEffect(() => { let isMounted = true; const loadData = async () => { if (globalCache) { isMounted && dispatch({type: 'LOAD_SUCCESS', payload: globalCache}); return; } if (isLoadingGlobal) return; isLoadingGlobal = true; dispatch({type: 'LOAD_START'}); try { const res = await fetch('你的国家数据API地址'); const data = await res.json(); globalCache = data; isMounted && dispatch({type: 'LOAD_SUCCESS', payload: data}); } catch (err) { isMounted && dispatch({type: 'LOAD_ERROR', payload: err}); } finally { isLoadingGlobal = false; } }; if (!state.countries) loadData(); return () => isMounted = false; }, [state.countries]); return ( <CountryContext.Provider value={state}> {children} </CountryContext.Provider> ); } export function useCountries() { const context = useContext(CountryContext); if (!context) { throw new Error('useCountries必须在CountryProvider内部使用'); } return context; }
组件中使用
import { useCountries } from './CountryContext'; function CountrySelector() { const { countries, loading, error } = useCountries(); if (loading) return <div>加载国家数据中...</div>; if (error) return <div>加载失败:{error.message}</div>; if (!countries) return null; return ( <select> {countries.map(c => ( <option key={c.iso} value={c.iso}> {c.flag} {c.name} </option> ))} </select> ); } // 根组件中包裹Provider function App() { return ( <CountryProvider> <CountrySelector /> {/* 其他需要使用国家数据的组件 */} </CountryProvider> ); }
优缺点
- ✅ 适合多组件共享数据场景
- ✅ 结合全局缓存,支持卸载重挂载复用
- ❌ 实现复杂度稍高
- ❌ 页面刷新需额外结合localStorage持久化
4. 使用SWR/React Query(高级方案)
如果项目需要处理更多数据请求场景(缓存过期、重试、手动刷新等),可以使用SWR或React Query这类专门的数据请求库,它们内置了完善的缓存和持久化机制。
SWR实现示例
# 安装SWR npm install swr
import useSWR from 'swr'; // 请求函数 const fetcher = async url => { const res = await fetch(url); if (!res.ok) throw new Error('请求失败'); return res.json(); }; function CountrySelector() { const { data: countries, error, isLoading } = useSWR( '你的国家数据API地址', fetcher, { // 禁用自动重新验证,仅首次加载 revalidateIfStale: false, revalidateOnFocus: false, revalidateOnReconnect: false, // 可选:用localStorage持久化缓存 persistCache: { provider: localStorage } } ); if (isLoading) return <div>加载国家数据中...</div>; if (error) return <div>加载失败:{error.message}</div>; if (!countries) return null; return ( <select> {countries.map(c => ( <option key={c.iso} value={c.iso}> {c.flag} {c.name} </option> ))} </select> ); }
优缺点
- ✅ 开箱即用的缓存、错误处理、并发控制
- ✅ 支持持久化、缓存过期、手动刷新等高级功能
- ❌ 需要引入第三方库,增加bundle体积(SWR体积很小)
方案选择建议
- 需求简单,无需页面刷新持久化:选模块级全局缓存
- 需要页面刷新后保留数据:选全局缓存+localStorage
- 多组件共享数据:选React Context+全局缓存(可结合localStorage)
- 项目有复杂数据请求需求:选SWR/React Query
内容的提问来源于stack exchange,提问作者andcl
相关产品推荐
相关产品推荐

