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

ReactJS全局Fetch缓存实现咨询:无需Redux与本地存储

Implementing a Global Fetch Cache with React Provider (No Redux/Persistent Storage)

Hey there! Let's build exactly what you're asking for: a global, in-memory fetch cache using a React Provider class component, paired with a reusable fetch wrapper you can import anywhere in your app. We'll skip Redux, avoid persistent storage, and ensure the cache only lives for the current app session (no retention on page refresh).

Step 1: Create the Cache Context and Provider Component

First, we'll set up a Context to share cache logic, and a class-based Provider to manage the in-memory cache state. The cache will store pending promises to handle concurrent calls to the same URL, preventing duplicate network requests.

import React, { Component, createContext } from 'react';

// Create a Context to share cached fetch functionality
const FetchCacheContext = createContext();

class FetchCacheProvider extends Component {
  constructor(props) {
    super(props);
    // Initialize in-memory cache: key = request URL, value = pending promise or resolved data
    this.state = {
      cache: {},
    };
  }

  // Core cached fetch logic
  cachedFetch = async (url, options = {}) => {
    const { cache } = this.state;

    // Return cached data/promise if it exists
    if (cache[url]) {
      return cache[url];
    }

    // Initiate fetch if no cache exists
    const fetchPromise = fetch(url, options)
      .then(response => {
        // Clone response to read it for cache and return to the caller
        const responseClone = response.clone();
        return responseClone.json().then(data => {
          // Update cache with resolved data (replace the pending promise)
          this.setState(prevState => ({
            cache: { ...prevState.cache, [url]: data },
          }));
          return data;
        });
      })
      .catch(error => {
        // Remove failed requests from cache to allow retries
        this.setState(prevState => {
          const updatedCache = { ...prevState.cache };
          delete updatedCache[url];
          return { cache: updatedCache };
        });
        throw error;
      });

    // Store the pending promise in cache to handle concurrent requests
    this.setState(prevState => ({
      cache: { ...prevState.cache, [url]: fetchPromise },
    }));

    return fetchPromise;
  };

  render() {
    return (
      <FetchCacheContext.Provider value={{ cachedFetch: this.cachedFetch }}>
        {this.props.children}
      </FetchCacheContext.Provider>
    );
  }
}

export { FetchCacheProvider, FetchCacheContext };

Step 2: Create Reusable Fetch Wrappers

We'll make multiple ways to access the cached fetch, so you can use it in functional components, class components, or even non-component utility files.

Option 1: Custom Hook for Functional Components

For functional components, a custom hook makes accessing the cached fetch straightforward:

import { useContext } from 'react';
import { FetchCacheContext } from './FetchCacheProvider';

export const useCachedFetch = () => {
  const { cachedFetch } = useContext(FetchCacheContext);
  return cachedFetch;
};

Use it in any functional component:

import { useCachedFetch } from './useCachedFetch';

const DataFetcher = () => {
  const cachedFetch = useCachedFetch();

  const loadData = async () => {
    try {
      const data = await cachedFetch('https://api.example.com/items');
      console.log('Loaded data:', data);
    } catch (err) {
      console.error('Fetch failed:', err);
    }
  };

  return <button onClick={loadData}>Load Items</button>;
};

Option 2: HOC for Class Components

For class components, wrap your component with this higher-order component to inject the cachedFetch method:

import { FetchCacheContext } from './FetchCacheProvider';
import React from 'react';

export const withCachedFetch = (Component) => {
  return class extends React.Component {
    static contextType = FetchCacheContext;

    render() {
      const { cachedFetch } = this.context;
      return <Component {...this.props} cachedFetch={cachedFetch} />;
    }
  };
};

Use it like this:

import { withCachedFetch } from './withCachedFetch';

class LegacyDataFetcher extends React.Component {
  loadData = async () => {
    try {
      const data = await this.props.cachedFetch('https://api.example.com/legacy-items');
      console.log('Loaded legacy data:', data);
    } catch (err) {
      console.error('Legacy fetch failed:', err);
    }
  };

  render() {
    return <button onClick={this.loadData}>Load Legacy Items</button>;
  }
}

export default withCachedFetch(LegacyDataFetcher);

Option 3: Utility for Non-Component Files

If you need to use cached fetch in a utility file (not a React component), set up a singleton to hold the cache instance once the Provider mounts:

// cachedFetchUtility.js
let cachedFetchInstance = null;

export const setCachedFetchInstance = (instance) => {
  cachedFetchInstance = instance;
};

export const cachedFetch = async (url, options) => {
  if (!cachedFetchInstance) {
    throw new Error('FetchCacheProvider must be mounted before using cachedFetch');
  }
  return cachedFetchInstance(url, options);
};

Update the Provider to set the instance on mount:

// In FetchCacheProvider.js
import { setCachedFetchInstance } from './cachedFetchUtility';

class FetchCacheProvider extends Component {
  componentDidMount() {
    setCachedFetchInstance(this.cachedFetch);
  }

  // ... rest of the component code
}

Now import it anywhere:

// In any utility file
import { cachedFetch } from './cachedFetchUtility';

export const preloadAppData = async () => {
  const appConfig = await cachedFetch('https://api.example.com/config');
  return appConfig;
};

Step 3: Wrap Your App with the Provider

Finally, wrap your root component with the FetchCacheProvider in your app's entry point to make the cache available globally:

import React from 'react';
import ReactDOM from 'react-dom/client';
import { FetchCacheProvider } from './FetchCacheProvider';
import App from './App';

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <FetchCacheProvider>
    <App />
  </FetchCacheProvider>
);

Key Features Breakdown

  • In-Memory Only: The cache lives in the Provider's state, so it's cleared when the page refreshes—exactly what you requested.
  • Concurrent Request Protection: Pending promises are stored in the cache, so multiple calls to the same URL while a request is in flight will all wait for the same response.
  • Error Recovery: Failed requests are removed from the cache, so subsequent calls will retry instead of returning a cached error.
  • Flexible Access: Use hooks, HOCs, or a utility function to access the cached fetch wherever you need it in your app.

内容的提问来源于stack exchange,提问作者Steven R

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:04:08