React应用初始化阶段多Redux请求完成/报错前显示全屏加载器的最佳实现方式
这是React项目初始化阶段很典型的加载场景,我给你分享几个实用的实现方案,你可以根据项目规模和现有技术栈来选择:
方案一:用Promise.allSettled直接跟踪所有请求
这个方案最直接,不需要改动太多现有Redux逻辑,适合快速实现:
步骤:
- 确保你的异步Action返回Promise:如果用的是Redux Thunk(或者Redux Toolkit的
createAsyncThunk),你的pagesRequest、articlesRequest这些Action本身就会返回Promise,这一步基本不用额外处理。 - 在
index.js中收集并等待所有请求:import React from 'react'; import ReactDOM from 'react-dom/client'; import { Provider } from 'react-redux'; import store from './store'; import App from './App'; import './index.css'; // 初始化基础请求 const initialRequests = [ store.dispatch(pagesRequest()), store.dispatch(articlesRequest()) ]; // 如果有token,追加额外请求 if (localStorage.ACCESS_TOKEN) { initialRequests.push( store.dispatch(profileRequest()), store.dispatch(notificationsRequest()) ); } // 先渲染全屏加载器 const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={store}> <div className="fullscreen-loader"> <div className="spinner"></div> </div> </Provider> ); // 等待所有请求完成(无论成功失败) Promise.allSettled(initialRequests) .then(() => { // 替换成实际应用内容 root.render( <Provider store={store}> <App /> </Provider> ); }); - 添加加载器样式:在
index.css里加上全屏加载的样式:.fullscreen-loader { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: rgba(255, 255, 255, 0.9); display: flex; justify-content: center; align-items: center; z-index: 9999; } .spinner { width: 50px; height: 50px; border: 5px solid #f3f3f3; border-top: 5px solid #3498db; border-radius: 50%; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
优缺点:
- ✅ 实现简单,侵入性低
- ❌ 没法在应用组件内感知加载状态,适合小型项目或不需要全局状态跟踪的场景
方案二:用Redux全局状态跟踪初始化加载状态
这个方案更适合中大型项目,能在应用任何组件里获取加载状态,扩展性更好:
步骤:
创建初始化加载状态的Slice:
// src/slices/initialLoadingSlice.js import { createSlice } from '@reduxjs/toolkit'; const initialState = { isLoading: true, pendingCount: 0 }; const initialLoadingSlice = createSlice({ name: 'initialLoading', initialState, reducers: { incrementPending: (state) => { state.pendingCount += 1; }, decrementPending: (state) => { state.pendingCount -= 1; // 所有请求完成后关闭加载 if (state.pendingCount === 0) { state.isLoading = false; } } } }); export const { incrementPending, decrementPending } = initialLoadingSlice.actions; export default initialLoadingSlice.reducer;别忘了把这个Slice加入到你的Redux Store中。
修改异步Action,添加状态跟踪:
以pagesRequest为例,其他几个Action做同样修改:// src/actions/pagesActions.js import { createAsyncThunk } from '@reduxjs/toolkit'; import { incrementPending, decrementPending } from '../slices/initialLoadingSlice'; export const pagesRequest = createAsyncThunk( 'pages/fetch', async (_, { dispatch }) => { dispatch(incrementPending()); try { const res = await fetch('/api/pages'); return await res.json(); } catch (err) { throw err; } finally { // 无论成功失败都标记请求完成 dispatch(decrementPending()); } } );如果是手写的Thunk,逻辑类似:在请求开始时dispatch
incrementPending,finally块里dispatchdecrementPending。在根组件中根据状态渲染加载器:
// src/App.js import { useSelector } from 'react-redux'; import './App.css'; function App() { const isInitialLoading = useSelector(state => state.initialLoading.isLoading); // 加载中时显示全屏加载器 if (isInitialLoading) { return ( <div className="fullscreen-loader"> <div className="spinner"></div> </div> ); } // 加载完成后渲染应用内容 return ( <div className="app-container"> {/* 你的导航栏、路由等内容 */} <Navbar /> <MainRoutes /> </div> ); } export default App;在
index.js中正常渲染应用:
现在不需要在index.js里手动处理Promise了,直接渲染App即可,Redux状态会自动控制加载器的显示:import React from 'react'; import ReactDOM from 'react-dom/client'; import { Provider } from 'react-redux'; import store from './store'; import App from './App'; import './index.css'; // 发起初始化请求 const initialRequests = [ store.dispatch(pagesRequest()), store.dispatch(articlesRequest()) ]; if (localStorage.ACCESS_TOKEN) { initialRequests.push( store.dispatch(profileRequest()), store.dispatch(notificationsRequest()) ); } const root = ReactDOM.createRoot(document.getElementById('root')); root.render( <Provider store={store}> <App /> </Provider> );
优缺点:
- ✅ 全局状态可访问,能在任意组件中根据加载状态做逻辑处理
- ✅ 扩展性好,后续新增初始化请求只需添加对应的状态跟踪
- ❌ 需要修改现有Action,增加少量代码量,但维护性更强
额外注意事项:
- 全屏加载器的样式要保证覆盖整个视口,z-index设得足够高,避免被其他元素遮挡
- 如果用户刷新页面,加载器会重新显示,符合用户预期
- 用
Promise.allSettled而不是Promise.all,因为all会在某个请求失败时直接reject,而我们需要等待所有请求(无论成败)都完成后再隐藏加载器
内容的提问来源于stack exchange,提问作者Игорь Быстревский
相关产品推荐
相关产品推荐

