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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 23:31:06