This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hhs/REACT/3. 生态工具篇/08-路由管理.md
T
2026-04-29 22:19:51 +08:00

193 lines
5.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
tags: [React, Router, Navigation, Frontend]
create time: 2026-04-29 22:07
---
# 路由管理
## 概述
前端路由是单页应用(SPA)的核心基础设施。本文档以 React Router v7 为主介绍路由配置、嵌套路由、懒加载、动态路由和守卫拦截等实战模式。
## React Router v7 核心概念
```mermaid
graph TB
A[BrowserRouter] --> B["哈希历史 vs HTML5 历史"]
B --> C[RouterProvider]
C --> D[Route 树]
D --> E["Element / Component / ComponentFunction"]
D --> F["Layout Route"]
F --> G["Child Routes"]
style A fill:#F4DBD6,color:#000
style E fill:#61DAFB,color:#000
```
### 基本结构
```tsx
// app.tsx(v7 推荐入口)
import { createRootRouteWithContext, createRouter, RouterProvider } from "@tanstack/react-router";
// 或传统 react-router-dom v6/v7
import { createBrowserRouter, RouterProvider } from "react-router-dom";
const router = createBrowserRouter([
{
path: "/",
element: <RootLayout />,
children: [
{ index: true, element: <HomePage /> },
{ path: "about", element: <AboutPage /> },
],
},
]);
function App() {
return <RouterProvider router={router} />;
}
```
## Layout Route 与嵌套路由
```tsx
{
// /dashboard 及其子路径共用此布局
path: "dashboard",
element: <DashboardLayout />, // 侧边栏 + Header 常驻
children: [
{ index: true, element: <DashboardHome /> }, // /dashboard → DashboardHome
{ path: "stats", element: <StatsPage /> }, // /dashboard/stats
{ path: "settings", element: <SettingsPage /> }, // /dashboard/settings
{ path: "settings/:tab", element: <SettingsTab /> }, // /dashboard/settings/profile
],
}
```
```jsx
// DashboardLayout.tsx
function DashboardLayout() {
return (
<div className="dashboard-layout">
<Sidebar />
<header><Navigation /></header>
<main>
<Outlet /> {/* v7 Outlet 替代了 v6的children渲染 */}
</main>
</div>
);
}
```
> [!note] Outlet vs Children
> - v6 用 `<Routes>` 内部定义子路由
> - v7 用 `<Outlet />` 显式标注嵌套出口,更清晰
## 路由参数获取
```tsx
import { useSearchParams, useParams, useNavigate, useLocation } from "react-router-dom";
function UserPage() {
const { id } = useParams(); // /user/:id → { id: "123" }
const [searchParams] = useSearchParams(); // ?q=hello&page=1
const location = useLocation(); // { pathname: "/user/123", search: "?q=hello" }
const navigate = useNavigate(); // navigate("/home") / navigate(-1)
const query = searchParams.get("q");
return <h1>User #{id}, searching for "{query}"</h1>;
}
```
### v7 新增:useRouteContext + Data APIs
```tsx
// v7 data routers 支持 loader/action
{
path: "posts/:postId",
loader: async ({ params }) => {
const res = await fetch(`/api/posts/${params.postId}`);
return res.json();
},
// loader 数据自动注入 component props
}
```
## 懒加载 Code Splitting
```tsx
import { lazy, Suspense } from "react";
// 路由级代码分割
const AdminPage = lazy(() => import("./pages/AdminPage"));
const SettingsPage = lazy(() => import("./pages/SettingsPage"));
{
path: "admin",
element: (
<Suspense fallback={<PageSpinner />}>
<AdminPage />
</Suspense>
),
}
```
> [!tip] 懒加载时机判断
> - 首屏路由(首页、登录页)**不要**懒加载
> - 低频访问页面(设置、管理员面板)适合懒加载
> - 每个 chunk 建议不超过 100KB gzipped
## 动态路由与 Splat 路由
```tsx
// :param —— 单个段匹配
{ path: "users/:userId", element: <UserProfile /> } // /users/42
// * splat —— 贪婪匹配剩余所有
{ path: "docs/*", element: <DocViewer /> } // /docs/a/b/c
{ path: "*", element: <NotFoundPage /> } // 404 fallback
```
## 路由守卫与权限控制
```tsx
// 方案1:条件渲染(简单场景)
function PrivateRoute({ children }: { children: React.ReactNode }) {
const { user } = useAuth();
if (!user) return <Navigate to="/login" replace />;
return <>{children}</>;
}
// 方案2:高阶包装
const withAuth = (Component: React.FC) => {
return (props: any) => {
const { loading, authenticated } = useAuth();
if (loading) return <Spinner />;
if (!authenticated) return <Navigate to="/login" replace />;
return <Component {...props} />;
};
};
// 方案3:v7 Loader 守卫(服务端前置检查)
{
path: "admin",
loader: () => {
if (!isAuthenticated()) throw new Response("", { status: 401 });
if (!isAdmin()) throw new Response("", { status: 403 });
},
element: <AdminPanel />,
}
```
## 导航 API 对比
| 方法 | 适用场景 | 是否保留历史记录 |
|------|----------|------------------|
| `navigate(path)` | 程序化跳转 | ✅ 有 history entry |
| `<Link to="/">Home</Link>` | 声明式导航 | ✅ 预加载 prefetch |
| `navigate(-1)` | 返回上一页 | ✅ |
| `<Navigate to="/login" replace />` | 替换当前 entry | ❌ 不增加 history |
## 关联笔记