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

5.0 KiB
Raw Blame History

tags, create time
tags create time
React
Router
Navigation
Frontend
2026-04-29 22:07

路由管理

概述

前端路由是单页应用(SPA)的核心基础设施。本文档以 React Router v7 为主介绍路由配置、嵌套路由、懒加载、动态路由和守卫拦截等实战模式。

React Router v7 核心概念

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

基本结构

// 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 与嵌套路由

{
  // /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
  ],
}
// 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 /> 显式标注嵌套出口,更清晰

路由参数获取

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

// 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

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 路由

// :param —— 单个段匹配
{ path: "users/:userId", element: <UserProfile /> }     // /users/42

// * splat —— 贪婪匹配剩余所有
{ path: "docs/*", element: <DocViewer /> }              // /docs/a/b/c
{ path: "*", element: <NotFoundPage /> }                // 404 fallback

路由守卫与权限控制

// 方案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

关联笔记