vault backup: 2026-04-29 22:19:51
This commit is contained in:
@@ -0,0 +1,192 @@
|
||||
---
|
||||
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 |
|
||||
|
||||
## 关联笔记
|
||||
Reference in New Issue
Block a user