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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 10:30:00