ReactJS全局Fetch缓存实现咨询:无需Redux与本地存储
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

