React中如何跟踪多API调用并实现全局加载屏?
全局加载屏实现方案
核心思路是跟踪所有活跃的请求状态,只要有一个请求在加载,就显示全局加载屏。结合useContext和useReducer是最优方案:useContext负责全局状态共享,useReducer负责处理加载状态的添加/移除逻辑,比单独用useState更易维护复杂状态。
步骤1:创建全局加载状态的Context和Reducer
先封装全局状态的管理逻辑,用Set存储当前正在加载的请求标识(确保唯一):
// LoadingContext.js import { createContext, useContext, useReducer } from 'react'; const LoadingContext = createContext(); // 处理加载状态的更新逻辑 const loadingReducer = (state, action) => { switch (action.type) { case 'ADD_LOADING': return new Set([...state, action.payload]); case 'REMOVE_LOADING': const updatedState = new Set(state); updatedState.delete(action.payload); return updatedState; default: return state; } }; export const LoadingProvider = ({ children }) => { const [loadingRequests, dispatch] = useReducer(loadingReducer, new Set()); // 只要有活跃请求,isLoading就为true const isLoading = loadingRequests.size > 0; return ( <LoadingContext.Provider value={{ dispatch, isLoading }}> {children} </LoadingContext.Provider> ); }; // 自定义Hook,简化Context调用 export const useLoading = () => useContext(LoadingContext);
步骤2:封装带全局状态的useAxios Hook
在你原有的useAxios基础上,包装一层,自动同步请求的loading状态到全局:
// useAxiosWithLoading.js import { useEffect } from 'react'; import { useAxios } from './useAxios'; // 引入你的原useAxios Hook import { useLoading } from './LoadingContext'; export const useAxiosWithLoading = (requestKey) => { const { dispatch } = useLoading(); const { data, error, loading, ...rest } = useAxios(); // 监听组件自身的loading状态,更新全局加载状态 useEffect(() => { if (loading) { dispatch({ type: 'ADD_LOADING', payload: requestKey }); } else { dispatch({ type: 'REMOVE_LOADING', payload: requestKey }); } // 组件卸载时清理状态,避免内存泄漏 return () => { dispatch({ type: 'REMOVE_LOADING', payload: requestKey }); }; }, [loading, dispatch, requestKey]); return { data, error, loading, ...rest }; };
requestKey需要是唯一标识,比如'componentA-user-list',避免不同请求的状态互相干扰。
步骤3:在组件中使用封装后的Hook
替换原有useAxios为useAxiosWithLoading,传入唯一的请求标识:
// ComponentA.js import { useAxiosWithLoading } from './useAxiosWithLoading'; const ComponentA = () => { const { data, error, loading } = useAxiosWithLoading('componentA-user-list'); if (loading) return <div>组件加载中...</div>; if (error) return <div>请求失败:{error.message}</div>; return ( <div> {data?.map(user => <div key={user.id}>{user.name}</div>)} </div> ); };
ComponentB、ComponentC同理,只需传入不同的requestKey即可。
步骤4:实现全局加载屏组件
监听全局isLoading状态,状态为true时显示全屏加载遮罩:
// GlobalLoading.js import { useLoading } from './LoadingContext'; const GlobalLoading = () => { const { isLoading } = useLoading(); if (!isLoading) return null; return ( <div style={{ position: 'fixed', inset: 0, backgroundColor: 'rgba(0, 0, 0, 0.5)', display: 'flex', justifyContent: 'center', alignItems: 'center', zIndex: 9999, color: 'white', fontSize: '1.2rem' }}> 全局加载中... </div> ); };
步骤5:在App根组件中配置Provider和加载屏
把整个应用包裹在LoadingProvider中,并挂载全局加载屏:
// App.js import { LoadingProvider } from './LoadingContext'; import GlobalLoading from './GlobalLoading'; import ComponentA from './ComponentA'; import ComponentB from './ComponentB'; import ComponentC from './ComponentC'; function App() { return ( <LoadingProvider> <GlobalLoading /> <div className="App"> <ComponentA /> <ComponentB /> <ComponentC /> </div> </LoadingProvider> ); } export default App;
为什么选择useContext+useReducer?
useContext:让所有组件能轻松访问全局加载状态,无需逐层传递props。useReducer:把加载状态的更新逻辑集中管理,比useState更适合处理“添加/移除”这类多步状态操作,后期扩展(比如添加请求超时、错误统计)更方便。
如果你的需求非常简单(比如只有3个固定请求),用useState+useContext也能实现,但useReducer的可维护性更强。
内容的提问来源于stack exchange,提问作者Muhammad
相关产品推荐
相关产品推荐

