第四章、路由配置
路由配置
一、安装依赖
npm install react-router-dom
二、推荐目录结构
src/
├── router/
│ └── index.tsx # 路由配置文件
├── pages/
│ ├── Home.tsx
│ ├── About/
│ │ ├── index.tsx
│ │ ├── LanguageI18n.tsx
│ │ └── ThemeSwitcher.tsx
│ ├── User/
│ │ ├── UserList.tsx
│ │ └── UserDetail.tsx
├── layout/
│ └── MainLayout.tsx # 公共布局
├── App.tsx
└── index.tsx
三、基础路由配置(src/router/index.tsx)
import React from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";import MainLayout from "@/layout/MainLayout";
import Home from "@/pages/Home";
import About from "@/pages/About/About";
import Company from "@/pages/About/LanguageI18n";
import Team from "@/pages/About/ThemeSwitcher";
import UserList from "@/pages/User/UserList";
import UserDetail from "@/pages/User/UserDetail";const router = createBrowserRouter([{path: "/",element: <MainLayout />,// 公共布局children: [{ index: true, element: <Home /> },// 默认路由{path: "about",element: <About />,children: [{ index: true, element: <Navigate to="languageI18n" replace /> },//设置默认子路由{ path: "languageI18n", element: <LanguageI18n /> },{ path: "themeSwitcher", element: <ThemeSwitcher /> },],},{ path: "users", element: <UserList /> },{ path: "users/:id", element: <UserDetail /> },// 动态参数],},
]);export default function AppRouter() {return <RouterProvider router={router} />;
}
四、在入口文件中加载路由
src/index.tsx
import React from "react";
import { createRoot } from "react-dom/client";
import "./styles/globals.css";
import "./index.css";
import { ThemeProvider } from "./context/ThemeContext";
import "./i18n";
import AppRouter from "./router";ReactDOM.createRoot(document.getElementById("root")!).render(<React.StrictMode><ThemeProvider><AppRouter /></ThemeProvider></React.StrictMode>
);
五、导航与跳转示例
src/pages/Home.tsx
import React from "react";
import { Link, useNavigate } from "react-router-dom";const Home: React.FC = () => {const navigate = useNavigate();const goToUser = () => {navigate("/users/42");};return (<div><h1>🏠 Home</h1><p>欢迎来到首页</p><Link to="/about">去关于页</Link><br /><button onClick={goToUser}>跳转到用户详情</button></div>);
};export default Home;
六、动态路由参数获取
src/pages/User/UserDetail.tsx
import React from "react";
import { useParams } from "react-router-dom";const UserDetail: React.FC = () => {const { id } = useParams<{ id: string }>();return (<div><h2>用户详情页</h2><p>当前用户ID: {id}</p></div>);
};export default UserDetail;
七、嵌套路由(src/pages/About/index.tsx)
index.tsx 作为父级页面,需要提供导航入口,并包含一个 <Outlet /> 来渲染子页面内容。
import React from "react";
import { Link, Outlet } from "react-router-dom";const About: React.FC = () => {return (<div><h1>关于</h1><nav style={{ marginBottom: "1rem" }}><Link to="languageI18n">国际化切换</Link> | <Link to="themeSwitcher">主题切换</Link></nav>{/* 子路由出口 */}<Outlet /></div>);
};export default About;
九、子页面
src/pages/About/LanguageI18n.tsx
import LanguageSwitcher from "@/components/LanguageSwitcher";
import React from "react";
import { useTranslation } from "react-i18next";const LanguageI18n: React.FC = () => {const { t } = useTranslation();return (<div className="p-8"><h1>{t("welcome")}</h1><p>{t("language")}: {t("change_language")}</p><LanguageSwitcher /></div>);
};export default LanguageI18n;
src/pages/About/ThemeSwitcher.tsx
import { useTheme } from "@/context/ThemeContext";
import React from "react";const ThemeSwitcher: React.FC = () => {const { mode, theme, setMode, toggleTheme } = useTheme();return (<div style={{ padding: "2rem" }}><h1>🌗 React 三种主题模式</h1><p>当前模式:{mode}</p><p>当前实际主题:{theme}</p><div style={{ display: "flex", gap: "10px", marginBottom: "20px" }}><button onClick={() => setMode("light")}>亮色模式</button><button onClick={() => setMode("dark")}>暗色模式</button><button onClick={() => setMode("system")}>跟随系统</button><button onClick={toggleTheme}>手动切换主题</button></div><p>示例文字会根据主题自动变色。</p></div>);
};export default ThemeSwitcher;
十、路由懒加载(代码分割)
一、为什么要用懒加载?
推荐:React.lazy + Suspense
在开发中,React 项目文件越来越大,如果所有页面在首次加载时都被打包下载,会导致:
- 首屏加载慢
- 打包体积大
- 用户等待时间长
解决方案: 按需加载(代码分割)
只有当用户访问某个路由页面时,才异步加载该页面代码。
这就是 React.lazy + Suspense 的核心功能。
二、改造后的 src/router/index.tsx
在(src/router/index.tsx)中
import React, { Suspense, lazy } from "react";
import { createBrowserRouter, RouterProvider } from "react-router-dom";// 使用 React.lazy 动态导入组件
const MainLayout = lazy(() => import("@/layout/MainLayout"));
const Home = lazy(() => import("@/pages/Home"));
const About = lazy(() => import("@/pages/About"));
const LanguageI18n = lazy(() => import("@/pages/About/languageI18n"));
const ThemeSwitcher = lazy(() => import("@/pages/About/ThemeSwitcher"));
const UserList = lazy(() => import("@/pages/User/UserList"));
const UserDetail = lazy(() => import("@/pages/User/UserDetail"));const router = createBrowserRouter([{path: "/",element: <MainLayout />,// 公共布局children: [{ index: true, element: <Home /> },// 默认路由{path: "about",element: <About />,children: [{ index: true, element: <Navigate to="languageI18n" replace /> },//设置默认子路由{ path: "languageI18n", element: <LanguageI18n /> },{ path: "themeSwitcher", element: <ThemeSwitcher /> },],},{ path: "users", element: <UserList /> },{ path: "users/:id", element: <UserDetail /> },// 动态参数],},
]);export default function AppRouter() {return <RouterProvider router={router} />;
}
三、全局 Suspense + Loading 动画
(1)Suspense的作用:
<Suspense fallback={<div>加载中...</div>}><About />
</Suspense>
Suspense是一个占位组件;- 当内部的
lazy组件正在异步加载时,React 会渲染fallback(占位内容); - 加载完成后,React 会自动替换为真实组件。
- 这使得用户体验更加平滑(不会白屏)。
通俗理解:
“页面在加载时显示一段提示(比如‘加载中…’),加载完再显示真正的页面。”
(2) 全局 Suspense + 优雅 Loading 动画 的懒加载版本。
src/
├── router/
│ └── index.tsx # 路由配置
├── components/
│ └── Loading.tsx # 全局加载动画
├── layout/
│ └── MainLayout.tsx
└── index.tsx
全局加载动画组件src/components/Loading.tsx
import React from "react";const Loading: React.FC = () => {return (<divstyle={{height: "100vh",display: "flex",justifyContent: "center",alignItems: "center",flexDirection: "column",fontSize: "18px",color: "#555",}}><divstyle={{width: 40,height: 40,border: "4px solid #ccc",borderTopColor: "#4f46e5",borderRadius: "50%",animation: "spin 1s linear infinite",}}></div><p style={{ marginTop: 10 }}>页面加载中...</p><style>{`@keyframes spin { from { transform: rotate(0deg); } to { transform: rotate(360deg); } }`}</style></div>);
};export default Loading;
src/router/index.tsx
import Loading from "@/components/Loading";
//...// 用 Suspense 包裹整个路由系统
const AppRouter: React.FC = () => {return (<Suspense fallback={<Loading />}><RouterProvider router={router} /></Suspense>);
};export default AppRouter;
全局 Suspense 的优势
✅ 只写一次 fallback,所有懒加载页面共用
✅ 不需要在每个 <Route> 包 <Suspense>
✅ 结构更清晰,可直接替换为统一的动画或骨架屏
十一、其他问题
1.别名使用
在
import MainLayout from "@/layout/MainLayout";时却报错。
这个问题通常是因为 TypeScript 编译器无法识别 Webpack 的别名设置。Webpack 在打包时会处理这些别名,但 TypeScript 在编译时也需要知道这些别名。需要同时在 Webpack 和 TypeScript 中配置别名。
修改 tsconfig.json
{"compilerOptions": {..."baseUrl": ".","paths": {"@/*": ["src/*"]}},"include": ["src/**/*"],"exclude": ["node_modules"]
}
完善 Webpack 配置webpack.common.js
resolve: {extensions: ['.ts', '.tsx', '.js', '.jsx'],alias: {'@': path.resolve(__dirname, '../src'),},},
注意补充完后,重启一下
2.React Router DOM 最常用方法和组件表格
核心组件
| 组件 | 用途 | 示例 | 使用频率 |
|---|---|---|---|
| BrowserRouter | 应用根路由容器 | <BrowserRouter><App /></BrowserRouter> | ⭐⭐⭐⭐⭐ |
| Routes | 路由配置容器 | <Routes><Route ... /></Routes> | ⭐⭐⭐⭐⭐ |
| Route | 定义单个路由 | <Route path="/" element={<Home />} /> | ⭐⭐⭐⭐⭐ |
| Link | 声明式导航链接 | <Link to="/about">关于</Link> | ⭐⭐⭐⭐⭐ |
| Outlet | 嵌套路由渲染位置 | <Outlet /> | ⭐⭐⭐⭐ |
| NavLink | 带激活状态的链接 | <NavLink to="/" className={({isActive}) => ...}> | ⭐⭐⭐ |
常用 Hooks
| Hook | 用途 | 示例 | 使用频率 |
|---|---|---|---|
| useNavigate | 编程式导航 | const navigate = useNavigate(); navigate('/path') | ⭐⭐⭐⭐⭐ |
| useParams | 获取路由参数 | const { id } = useParams(); | ⭐⭐⭐⭐⭐ |
| useLocation | 获取当前位置信息 | const location = useLocation(); | ⭐⭐⭐⭐ |
| useSearchParams | 处理URL查询参数 | const [params, setParams] = useSearchParams(); | ⭐⭐⭐⭐ |
| useRoutes | 对象形式定义路由 | const routes = useRoutes([...]); | ⭐⭐⭐ |
导航组件对比
| 特性 | Link | NavLink | useNavigate |
|---|---|---|---|
| 类型 | 声明式 | 声明式 | 编程式 |
| 激活状态 | ❌ | ✅ | ❌ |
| 使用场景 | 普通链接 | 导航菜单 | 事件处理、表单提交 |
参数获取对比
| 场景 | 使用 Hook | 示例 |
|---|---|---|
| 路径参数 | useParams | /user/:id → const {id} = useParams() |
| 查询参数 | useSearchParams | ?page=1 → const [params] = useSearchParams() |
| 状态传递 | useLocation | navigate('/path', {state: {data}}) |
路由配置及菜单数据
(1)分清楚路由配置的数据和菜单导航的渲染数据

在菜单渲染数据里子菜单漏写父路径会导致404

(2)<Outlet />子路由出口
在src/pages/About/index.tsx中


在MainLayout.tsx中
首页、关于、用户等都是子路由


所以MainLayout.tsx是公共布局,主布局页面
