React Router中如何判断Root Layout子组件加载状态并实现导航栏加载提示?
问题本质
useNavigation的state仅在路由处于加载阶段时才会更新,触发场景包括:
- 使用
lazy异步加载路由组件 - 路由配置了
loader函数且正在加载数据
你当前的代码中,所有页面组件都是同步导入的,路由切换瞬间完成,没有加载阶段,因此navigation.state始终保持idle。
解决方案
1. 针对路由切换(组件加载)显示状态
将页面组件改为懒加载,让路由切换产生加载阶段:
修改App.tsx,用React.lazy包装组件:
import { lazy, Suspense } from 'react'; import RootLayout from "@pages/Root" import { RouterProvider, createBrowserRouter } from "react-router-dom" // 懒加载页面组件 const DetailsPage = lazy(() => import("@pages/Details")); const EpisodePage = lazy(() => import("@pages/Episode")); const HomePage = lazy(() => import("@pages/Home")); const router = createBrowserRouter([ { path: '/', element: ( <Suspense fallback={null}> {/* 不需要全局fallback,RootLayout自己控制加载状态 */} <RootLayout /> </Suspense> ), children: [ { index: true, element: <HomePage /> }, { path: '/podcast/:podcastId', element: <DetailsPage /> }, { path: '/podcast/:podcastId/episode/:episodeId', element: <EpisodePage /> } ] } ]) const App = () => { return <RouterProvider router={router} /> } export default App
在RootLayout中判断加载状态并显示圆点:
import Navbar from '@components/UI/Navbar' import { Outlet, useNavigation } from 'react-router-dom' export const RootLayout = () => { const navigation = useNavigation() const isRouteLoading = navigation.state === "loading" return <> <Navbar> <h1>Podcasters</h1> {/* 加载圆点样式可自行调整 */} {isRouteLoading && <span style={{marginLeft:8, fontSize:18, color:'#666'}}>●</span>} </Navbar> <Outlet /> </> } export default RootLayout
2. 针对页面数据加载显示状态
如果是页面内部加载数据(比如详情页获取接口数据),需要额外维护加载状态,可通过React Context传递到导航栏:
第一步:创建LoadingContext
// contexts/LoadingContext.tsx import { createContext, useContext, useState, ReactNode } from 'react'; type LoadingContextType = { isDataLoading: boolean; setIsDataLoading: (value: boolean) => void; }; const LoadingContext = createContext<LoadingContextType>({ isDataLoading: false, setIsDataLoading: () => {}, }); export const LoadingProvider = ({ children }: { children: ReactNode }) => { const [isDataLoading, setIsDataLoading] = useState(false); return ( <LoadingContext.Provider value={{ isDataLoading, setIsDataLoading }}> {children} </LoadingContext.Provider> ); }; export const useLoading = () => useContext(LoadingContext);
第二步:在App中包裹Provider
// App.tsx // ...其他导入 import { LoadingProvider } from './contexts/LoadingContext'; const App = () => { return ( <LoadingProvider> <RouterProvider router={router} /> </LoadingProvider> ); };
第三步:在RootLayout中合并状态
import { useLoading } from '../contexts/LoadingContext'; export const RootLayout = () => { const navigation = useNavigation(); const { isDataLoading } = useLoading(); // 合并路由加载和数据加载状态 const showLoadingDot = navigation.state === "loading" || isDataLoading; return <> <Navbar> <h1>Podcasters</h1> {showLoadingDot && <span style={{marginLeft:8, fontSize:18, color:'#666'}}>●</span>} </Navbar> <Outlet /> </> }
第四步:在页面组件中更新状态
以DetailsPage为例:
import { useState, useEffect } from 'react'; import { useParams } from 'react-router-dom'; import { useLoading } from '../contexts/LoadingContext'; const DetailsPage = () => { const { podcastId } = useParams(); const { setIsDataLoading } = useLoading(); const [data, setData] = useState<null | PodcastType>(null); useEffect(() => { const fetchData = async () => { setIsDataLoading(true); try { const res = await fetch(`/api/podcast/${podcastId}`); const result = await res.json(); setData(result); } catch (err) { console.error('数据加载失败:', err); } finally { setIsDataLoading(false); } }; fetchData(); }, [podcastId, setIsDataLoading]); return data ? <div>{data.title}</div> : <div>内容加载中...</div>; }; export default DetailsPage;
3. 使用React Router Loader函数(推荐)
给路由配置loader函数,在路由切换时预加载数据,此时useNavigation会自动进入loading状态:
修改App.tsx的路由配置:
const router = createBrowserRouter([ { path: '/', element: <RootLayout />, children: [ { index: true, element: <HomePage /> }, { path: '/podcast/:podcastId', element: <DetailsPage />, // 定义路由loader loader: async ({ params }) => { const res = await fetch(`/api/podcast/${params.podcastId}`); if (!res.ok) throw new Error('数据加载失败'); return res.json(); } }, { path: '/podcast/:podcastId/episode/:episodeId', element: <EpisodePage />, loader: async ({ params }) => { const res = await fetch(`/api/podcast/${params.podcastId}/episode/${params.episodeId}`); if (!res.ok) throw new Error('数据加载失败'); return res.json(); } } ] } ])
在页面中用useLoaderData获取数据:
// DetailsPage.tsx import { useLoaderData } from 'react-router-dom'; const DetailsPage = () => { const data = useLoaderData() as PodcastType; return <div>{data.title}</div>; }; export default DetailsPage;
此时路由切换时,navigation.state会自动变为loading,RootLayout中直接用该状态即可显示加载圆点。
总结
- 同步路由切换不会触发
useNavigation的loading状态,需通过懒加载组件或loader函数制造加载阶段 - 页面内部数据加载需自行维护状态,通过Context可传递到导航栏统一展示
- 结合路由加载和数据加载状态,能实现完整的全局加载反馈
内容的提问来源于stack exchange,提问作者Sky
相关产品推荐
相关产品推荐

