目 录CONTENT

文章目录

React(二十)React路由

React Router V7 完全教程:从入门到精通

本教程面向零基础到进阶的 React 开发者,用生活化语言系统讲解 React Router V7 的全套用法。所有代码均使用 React 18+ 函数式组件,兼容 V7 最新 API,可直接运行。


一、环境搭建与依赖安装

1.1 创建 React 项目

推荐使用 Vite(速度快、配置少):

# npm
npm create vite@latest react-router-demo -- --template react

# yarn
yarn create vite react-router-demo --template react

# pnpm
pnpm create vite react-router-demo --template react

进入项目目录:

cd react-router-demo

1.2 安装 React Router V7

React Router V7 的核心依赖只有 react-router 一个包,不再需要单独安装 @types/react-router-dom 等额外包。

# npm
npm install react-router

# yarn
yarn add react-router

# pnpm
pnpm add react-router

V7 关键变化:V7 将 V6 中分离的 react-router-domreact-router 合并为一个统一的 react-router 包。不再需要安装 react-router-dom

1.3 验证安装是否成功

检查 package.json 中是否包含:

{
  "dependencies": {
    "react": "^18.3.0",
    "react-dom": "^18.3.0",
    "react-router": "^7.0.0"
  }
}

1.4 最小化启动示例

// main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";

import { createBrowserRouter, RouterProvider } from "react-router";

// 临时首页
function Home() {
  return <h1>React Router V7 教程</h1>;
}

const router = createBrowserRouter([
  { path: "/", Component: Home, index: true },
]);

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <RouterProvider router={router} />
  </StrictMode>,
);

启动项目验证:

npm run dev

浏览器打开 http://localhost:5173,看到"React Router V7 教程"即表示安装成功。


二、路由初体验:两个页面的切换

2.1 创建页面组件

// pages/Home.jsx
function Home() {
  return <h1>首页</h1>;
}
export default Home;
// pages/About.jsx
function About() {
  return <h1>关于页</h1>;
}
export default About;

2.2 配置路由表

// router.jsx
import { createBrowserRouter } from "react-router";
import Home from "./pages/Home";
import About from "./pages/About";

export const router = createBrowserRouter([
  { path: "/", Component: Home }, // 路径 / 渲染 Home
  { path: "/about", Component: About }, // 路径 /about 渲染 About
]);

2.3 挂载路由

// main.jsx
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { RouterProvider } from "react-router";
import { router } from "./router";

createRoot(document.getElementById("root")).render(
  <StrictMode>
    <RouterProvider router={router} />
  </StrictMode>,
);

踩坑提醒:V7 使用 RouterProvider + createBrowserRouter 的组合方式。V6 中常见的 <BrowserRouter> + <Routes> JSX 写法在 V7 中不再是推荐的顶层方式(但 V7 仍兼容该写法)。

2.4 添加导航链接

// App.jsx
import { Link, NavLink, Outlet } from "react-router";

function App() {
  return (
    <div>
      <nav>
        {/* Link:基础跳转,不会高亮 */}
        <Link to="/">首页</Link>

        {/* NavLink:自动添加 active 类名,适合导航栏 */}
        <NavLink
          to="/about"
          className={({ isActive }) => (isActive ? "active" : "")}
        >
          关于
        </NavLink>
      </nav>

      {/* Outlet:子路由的渲染出口 */}
      <Outlet />
    </div>
  );
}

export default App;

三、路由失败处理

3.1 404 页面配置

在路由表末尾添加兜底路由:

// router.jsx
import { createBrowserRouter } from 'react-router'

// ... 其他路由
{ path: '*', Component: NotFound }  // 放在最后,拦截所有未匹配路径
// pages/NotFound.jsx
import { Link } from "react-router";

function NotFound() {
  return (
    <div style={{ textAlign: "center", padding: 50 }}>
      <h1>404 - 页面未找到</h1>
      <p>
        你访问的页面不存在,返回 <Link to="/">首页</Link>
      </p>
    </div>
  );
}

export default NotFound;

3.2 路由鉴权失败的跳转

创建鉴权守卫组件

// components/AuthGuard.jsx
import { Navigate, useLocation } from "react-router";

function AuthGuard({ children }) {
  const location = useLocation();

  // 模拟判断:实际项目中从 store 或 cookie 中读取
  const isLoggedIn = localStorage.getItem("token");

  if (!isLoggedIn) {
    // 记住用户想去的路径,登录后跳回
    return <Navigate to="/login" state={{ from: location }} replace />;
  }

  return children;
}

export default AuthGuard;

路由中应用鉴权

// router.jsx
<Route
  path="/admin"
  element={
    <AuthGuard>
      <AdminPage />
    </AuthGuard>
  }
/>

3.3 全局错误边界

React Router V7 支持 errorElement 属性,在路由表级别捕获所有异常。

// pages/ErrorPage.jsx
import { useRouteError, Link } from "react-router";

function ErrorPage() {
  const error = useRouteError();

  return (
    <div style={{ textAlign: "center", padding: 50 }}>
      <h1>页面出错了</h1>
      <p>{error?.message || "未知错误"}</p>
      <Link to="/">返回首页</Link>
    </div>
  );
}

export default ErrorPage;
// router.jsx - 根路由上配置全局错误页
import { createBrowserRouter } from "react-router";
import ErrorPage from "./pages/ErrorPage";

export const router = createBrowserRouter([
  {
    path: "/",
    errorElement: <ErrorPage />, // 全局错误处理
    children: [
      // ...所有子路由
    ],
  },
]);

生活化类比errorElement 就像大楼里的消防系统——不管哪层着火(哪页出错),都会启动统一的灭火(错误展示)流程,不至于整栋大楼坍塌。


四、嵌套路由

4.1 什么是嵌套路由?

现实生活中,很多页面结构是嵌套的。比如一个后台管理系统:

/admin                   → 侧边栏(不变)
  /admin/dashboard       → 仪表盘内容
  /admin/users           → 用户管理内容
  /admin/settings        → 设置内容

侧边栏始终显示,但右侧内容区根据 URL 切换不同的子页面。这就是嵌套路由的典型场景。

4.2 <Outlet /> — 子路由的"插槽"

<Outlet /> 就像一面智能墙,在父组件中挖了一个洞,子路由匹配时自动将子组件填入这个洞。

// pages/AdminLayout.jsx
import { NavLink, Outlet } from "react-router";

function AdminLayout() {
  return (
    <div style={{ display: "flex" }}>
      {/* 侧边栏:始终显示 */}
      <nav style={{ width: 200, background: "#f0f0f0", padding: 20 }}>
        <NavLink to="/admin/dashboard">仪表盘</NavLink>
        <br />
        <NavLink to="/admin/users">用户管理</NavLink>
        <br />
        <NavLink to="/admin/settings">系统设置</NavLink>
      </nav>

      {/* 内容区:子路由内容会渲染在这里 */}
      <main style={{ flex: 1, padding: 20 }}>
        <Outlet />
      </main>
    </div>
  );
}

export default AdminLayout;

4.3 配置多级嵌套路由

// router.jsx
import { createBrowserRouter } from "react-router";
import App from "./App";
import AdminLayout from "./pages/AdminLayout";
import Dashboard from "./pages/Dashboard";
import Users from "./pages/Users";
import Settings from "./pages/Settings";

export const router = createBrowserRouter([
  {
    path: "/",
    Component: App,
    children: [
      // 一级子路由放这里
    ],
  },
  {
    path: "/admin",
    Component: AdminLayout, // 父组件:侧边栏布局
    children: [
      { path: "dashboard", Component: Dashboard }, // /admin/dashboard
      { path: "users", Component: Users }, // /admin/users
      { path: "settings", Component: Settings }, // /admin/settings
    ],
  },
]);

踩坑提醒:子路由的 path 不要写 / 前缀。path: 'dashboard' 会自动拼接为 /admin/dashboard。如果写成 path: '/dashboard',则会变成根路径下的 /dashboard,脱离嵌套结构。


五、路由跳转

5.1 声明式导航

声明式导航是指通过 JSX 组件声明跳转行为,不需要写 JavaScript 逻辑。

组件 用途 特色
<Link to="/page"> 基础跳转 渲染为 <a> 标签,不会刷新页面
<NavLink> 导航栏跳转 自动感知当前 URL,可应用高亮样式
<Navigate to="/page"> 重定向 渲染时立即跳转,常用于鉴权跳转
// 基础跳转
<Link to="/about">去关于页</Link>

// 带高亮的导航跳转
<NavLink
  to="/admin/users"
  className={({ isActive }) => isActive ? 'active-link' : ''}
>
  用户管理
</NavLink>

// 条件跳转(鉴权失败时)
{!isLoggedIn && <Navigate to="/login" replace />}

5.2 编程式导航(useNavigate)

编程式导航是在 JavaScript 代码中主动触发跳转,适合"点击按钮后先处理逻辑再跳转"的场景。

import { useNavigate } from "react-router";

function LoginPage() {
  const navigate = useNavigate();

  const handleLogin = async () => {
    // 先执行业务逻辑
    const success = await loginApi({ username, password });

    if (success) {
      navigate("/dashboard"); // 前进到新页面
      // navigate(-1)                  // 返回上一页
      // navigate('/', { replace: true })  // 替换当前历史记录,不可回退
    }
  };

  return <button onClick={handleLogin}>登录</button>;
}

5.3 useLocation — 获取当前路由信息

useLocation 返回包含当前路径、搜索参数、路由状态的对象。

import { useLocation } from "react-router";

function CurrentPath() {
  const location = useLocation();

  return (
    <div>
      <p>当前路径:{location.pathname}</p>
      <p>搜索参数:{location.search}</p>
      <p>路由状态:{JSON.stringify(location.state)}</p>
    </div>
  );
}

5.4 带参数跳转

// 传路由状态(state)
navigate("/dashboard", { state: { from: "login", timestamp: Date.now() } });

// 在目标页面读取
const location = useLocation();
console.log(location.state?.from); // 'login'

踩坑提醒useLocation().state 仅在本次路由跳转中有效,刷新页面后 state 会丢失。需要持久化就用 useSearchParams(体现在 URL 上)或 Store(如 Zustand)。


六、Loader 与 Action

6.1 什么是 Loader?

Loader 是在路由渲染之前执行的函数,用来提前获取页面所需的数据。

类比:去餐厅吃饭。你点完菜(访问路由),服务员(Loader)会先根据你的要求从后厨拿取食材(从后端获取数据),准备好了再给你上菜(渲染页面)。你不需要先看到空盘子再等菜。

6.2 Loader 使用示例:用户列表

定义 Loader

// pages/users/loader.js
export async function usersLoader() {
  // 模拟从 API 获取数据
  const res = await fetch("https://jsonplaceholder.typicode.com/users");
  if (!res.ok) throw new Error("用户列表加载失败");
  return { users: await res.json() };
}

路由配置中挂载 Loader

// router.jsx
{
  path: '/users',
  Component: UsersPage,
  loader: usersLoader,           // 路由渲染前执行
}

组件中使用 useLoaderData 获取数据

// pages/UsersPage.jsx
import { useLoaderData } from "react-router";

function UsersPage() {
  const { users } = useLoaderData(); // 获取 loader 返回的数据

  return (
    <div>
      <h1>用户列表</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            {user.name} - {user.email}
          </li>
        ))}
      </ul>
    </div>
  );
}

export default UsersPage;

6.3 什么是 Action?

Action 是个可以修改后端数据的动态路由的专用路由操作。V7 中 Action 的处理方式有所调整:

Action 是路由的 action 函数,在表单提交(<Form>)或调用 useSubmit 时触发,用来处理数据的增删改操作。

// pages/users/action.js
export async function usersAction({ request }) {
  const formData = await request.formData();
  const username = formData.get("username");

  // 模拟提交到 API
  const res = await fetch("https://jsonplaceholder.typicode.com/users", {
    method: "POST",
    body: JSON.stringify({ name: username }),
    headers: { "Content-Type": "application/json" },
  });

  if (!res.ok) throw new Error("提交失败");
  return { success: true, data: await res.json() };
}
// router.jsx
{
  path: '/users',
  Component: UsersPage,
  loader: usersLoader,
  action: usersAction,         // 表单提交时执行
}
// pages/UsersPage.jsx
import { Form, useLoaderData, useActionData } from "react-router";

function UsersPage() {
  const { users } = useLoaderData();
  const actionData = useActionData(); // Action 返回的数据

  return (
    <div>
      {/* Form 提交会自动调用路由的 action */}
      <Form method="post">
        <input name="username" placeholder="新增用户名" />
        <button type="submit">添加</button>
      </Form>

      {actionData?.success && <p>添加成功!</p>}

      <ul>
        {users.map((u) => (
          <li key={u.id}>{u.name}</li>
        ))}
      </ul>
    </div>
  );
}

6.4 Loader 与 Action 的执行时机总结

函数 触发时机 获取数据
loader 路由渲染前(GET 类操作) useLoaderData()
action 表单提交(POST/PUT/DELETE) useActionData()

踩坑提醒loaderaction 里抛出错误会被路由的 errorElement 捕获,所以不必在组件里写 try-catch,把错误处理交给路由层统一管理即可。


七、路由参数与状态获取

7.1 动态路由参数:useParams()

用于从 URL 路径中提取变量,比如 /users/123 中的 123

// router.jsx
{ path: '/users/:userId', Component: UserDetail }
// pages/UserDetail.jsx
import { useParams, useNavigate } from "react-router";

function UserDetail() {
  const { userId } = useParams(); // 从 URL 中提取 userId
  const navigate = useNavigate();

  return (
    <div>
      <h1>用户详情 #{userId}</h1>
      <button onClick={() => navigate(-1)}>返回列表</button>
    </div>
  );
}

适用场景:用户详情、文章详情、商品页面等——URL 本身就代表唯一资源。

7.2 搜索参数(Query String):useSearchParams()

用于 URL 中 ? 后面的键值对参数,如 /search?keyword=react&page=2

import { useSearchParams } from "react-router";

function SearchPage() {
  const [searchParams, setSearchParams] = useSearchParams();

  // 读取参数
  const keyword = searchParams.get("keyword") || "";
  const page = searchParams.get("page") || "1";

  // 更新参数(会触发 URL 变化和重新渲染)
  const handleSearch = (newKeyword) => {
    setSearchParams({ keyword: newKeyword, page: "1" });
  };

  return (
    <div>
      <input
        value={keyword}
        onChange={(e) => handleSearch(e.target.value)}
        placeholder="搜索..."
      />
      <p>
        当前搜索:{keyword},第 {page} 页
      </p>
    </div>
  );
}

适用场景:搜索页、列表筛选、分页——参数变化反映了用户的操作意图。

7.3 路由状态(State):navigate 传参

不体现在 URL 上,通过内存传递的数据。

// 发送方
navigate("/result", { state: { score: 95, name: "张三" } });

// 接收方
import { useLocation } from "react-router";
const location = useLocation();
console.log(location.state?.score); // 95

适用场景:表单提交成功后的结果页、登录后跳回的来源页——不需要暴露在 URL 中的临时数据。

7.4 三种参数的对比

方式 数据存在哪里 刷新后是否保留 适用场景
useParams() URL 路径 资源标识(如用户 ID)
useSearchParams() URL 查询串 搜索、筛选、分页
state 浏览器内存 临时跨页面传递

八、默认子路由与子路由错误页

8.1 默认子路由(Index Route)

当访问父路径 /admin 时,需要自动展示一个默认页面(如仪表盘),这就是 index 路由的用途。

// router.jsx
{
  path: '/admin',
  Component: AdminLayout,
  children: [
    { index: true, Component: Dashboard },   // 默认子路由,访问 /admin 时展示
    { path: 'users',     Component: Users     },
    { path: 'settings',  Component: Settings  },
  ],
}

当用户访问 /admin 时,AdminLayout 渲染,<Outlet /> 处自动显示 Dashboard 组件。

path: '' 的区别:V7 推荐使用 index: true 声明默认子路由,语义更清晰。

8.2 子路由专属错误页

不同模块可能需要不同的错误展示方式。V7 支持为每个路由子树设置独立的 errorElement

// router.jsx
{
  path: '/admin',
  Component: AdminLayout,
  errorElement: <AdminErrorPage />,    // admin 子树专属错误页
  children: [
    { index: true, Component: Dashboard },
    { path: 'users', Component: Users, errorElement: <UserErrorPage /> },
    // users 路由还可以有自己的错误页,覆盖父级
  ],
},
{
  path: '/shop',
  Component: ShopLayout,
  errorElement: <ShopErrorPage />,     // shop 子树专属错误页
  children: [...],
}
// pages/AdminErrorPage.jsx
import { useRouteError, Link } from "react-router";

function AdminErrorPage() {
  const error = useRouteError();

  return (
    <div style={{ padding: 20 }}>
      <h2>管理后台数据加载失败</h2>
      <p>{error?.message}</p>
      <Link to="/">返回首页</Link>
    </div>
  );
}

export default AdminErrorPage;

业务价值:不同模块的错误提示风格统一 —— 比如管理后台的错误页显示"请联系管理员",而商城模块的错误页显示"商品加载失败,返回浏览"。


九、路由懒加载

9.1 为什么要懒加载?

当应用有几十上百个页面时,打包后所有的 JS 代码会塞进一个巨大的文件里。用户打开首页时,实际上也把"设置页""用户管理页"等根本没访问的代码下载了 —— 白屏时间变长。

懒加载的原理:按路由拆分代码包。用户访问 /dashboard 只下载 Dashboard 页面所需的代码,访问 /settings 时才下载 Settings 的代码。

9.2 React.lazy() + Suspense 实现

// router.jsx
import { createBrowserRouter } from "react-router";
import { lazy, Suspense } from "react";

// 懒加载路由组件
const Home = lazy(() => import("./pages/Home"));
const About = lazy(() => import("./pages/About"));
const Dashboard = lazy(() => import("./pages/Dashboard"));
const Settings = lazy(() => import("./pages/Settings"));

V7 推荐方式 —— 使用 lazy 属性的路由级懒加载

// router.jsx
import { createBrowserRouter } from "react-router";

export const router = createBrowserRouter([
  {
    path: "/",
    // 使用 lazy 属性直接懒加载组件
    lazy: () => import("./App"),
  },
  {
    path: "/about",
    lazy: () => import("./pages/About"),
  },
  {
    path: "/admin",
    lazy: () => import("./pages/AdminLayout"),
    children: [
      {
        index: true,
        lazy: () => import("./pages/Dashboard"),
      },
      {
        path: "users",
        lazy: () => import("./pages/Users"),
      },
    ],
  },
]);

9.3 加载占位组件

// components/LoadingSpinner.jsx
function LoadingSpinner() {
  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        height: "100vh",
      }}
    >
      <div
        style={{
          width: 40,
          height: 40,
          border: "4px solid #e0e0e0",
          borderTopColor: "#1976d2",
          borderRadius: "50%",
          animation: "spin 1s linear infinite",
        }}
      />
      <span style={{ marginLeft: 12 }}>页面加载中...</span>
    </div>
  );
}

export default LoadingSpinner;

对应的 CSS(添加到全局样式):

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

9.4 使用 Suspense 包裹

// main.jsx
import { Suspense } from "react";
import { RouterProvider } from "react-router";
import LoadingSpinner from "./components/LoadingSpinner";
import { router } from "./router";

createRoot(document.getElementById("root")).render(
  <Suspense fallback={<LoadingSpinner />}>
    <RouterProvider router={router} />
  </Suspense>,
);

9.5 懒加载失败的重试逻辑

当懒加载的网络请求失败(如 CDN 挂了、用户断网),需要给用户重试机会。

// main.jsx - 增加全局错误捕获
import { Component, Suspense } from "react";

class LazyErrorBoundary extends Component {
  state = { hasError: false };

  static getDerivedStateFromError() {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return (
        <div>
          <h2>页面加载失败</h2>
          <button onClick={() => window.location.reload()}>重新加载</button>
        </div>
      );
    }
    return this.props.children;
  }
}

// 使用双层包裹
createRoot(document.getElementById("root")).render(
  <LazyErrorBoundary>
    <Suspense fallback={<LoadingSpinner />}>
      <RouterProvider router={router} />
    </Suspense>
  </LazyErrorBoundary>,
);

9.6 指定 chunk 名称便于调试

// 魔法注释:打包后生成 dashboard-xxx.js,方便在 Network 面板识别
lazy: () => import(/* webpackChunkName: "dashboard" */ './pages/Dashboard'),

9.7 验证代码分割效果

  1. 打开浏览器 DevTools → Network 面板。
  2. 勾选 JS 类型过滤。
  3. 首次访问首页,观察加载了哪些 JS 文件。
  4. 点击导航到其他页面,观察是否有新的 JS 文件请求发出。

十、完整项目结构示例

react-router-demo/
├── index.html
├── package.json
├── src/
│   ├── main.jsx                  # 入口文件,挂载路由
│   ├── router.jsx                # 路由表配置(核心)
│   ├── App.jsx                   # 根布局组件
│   ├── components/
│   │   ├── AuthGuard.jsx         # 鉴权守卫
│   │   ├── ErrorBoundary.jsx    # 错误边界
│   │   └── LoadingSpinner.jsx    # 加载占位
│   └── pages/
│       ├── Home.jsx
│       ├── About.jsx
│       ├── NotFound.jsx
│       ├── ErrorPage.jsx         # 全局错误页
│       ├── Login.jsx
│       ├── AdminLayout.jsx       # 后台布局(含侧边栏)
│       ├── Dashboard.jsx
│       ├── Users.jsx
│       ├── UserDetail.jsx
│       └── Settings.jsx
└── vite.config.js

十一、常见踩坑汇总

问题 原因 解法
子路由不渲染 父组件中忘记写 <Outlet /> 在父组件的 JSX 中加上 <Outlet />
跳转后页面空白 路由表中没有匹配的路径 检查 path 拼写,末尾加 path: '*' 兜底
useNavigate() 报错 组件不在 <RouterProvider> 子树中 确保该组件是路由表中的合法路由组件
Loader 数据为 null 忘记在组件中使用 useLoaderData() 确认组件是路由表配置中的 Component
懒加载后页面空白 没有用 <Suspense> 包裹 RouterProvider 外层加 Suspense fallback
NavLink 高亮不生效 路径匹配规则不对 to="/admin" 不会匹配 /admin/users,用 end 属性精确匹配
刷新后 state 丢失 state 存在内存中 改用 useSearchParams() 或 localStorage 持久化

十二、V7 与旧版本核心差异速查

特性 V6 写法 V7 写法
创建路由 createBrowserRouter() + <RouterProvider> 相同,推荐写法
路由定义 element: <Comp /> Component: Comp(大写 C,不用 JSX)
默认子路由 path: ''index: true 推荐 index: true
数据获取 loader / useLoaderData() 相同,但 V7 加强了类型推导
包导入 react-router-dom 统一为 react-router
JSX 路由写法 <BrowserRouter><Routes> 仍兼容,但推荐 createBrowserRouter 配置式

十三、调试方法

13.1 查看当前路由状态

在浏览器控制台中安装 React DevTools,选择对应的组件后可以在右侧查看 props 中的 locationparams 等信息。

13.2 启用路由日志

// 在 App 组件中临时添加
import { useLocation } from "react-router";

useEffect(() => {
  console.log("路由变化:", location.pathname, location.search);
}, [location]);

13.3 常见报错的排查顺序

  1. 浏览器控制台有没有红色报错?
  2. 报错信息里的路径和路由表中是否一致?
  3. <Outlet /> 是否渲染在正确的父组件中?
  4. loader 返回的数据格式是否正确?

十四、课后练习题

练习 1:基础路由搭建

目标:创建一个包含"首页 / 关于 / 404"的 React Router V7 应用。

要求

  • 使用 createBrowserRouter 配置路由表。
  • 访问不存在的路径时显示 404 页面。
  • 使用 NavLink 实现导航高亮。
  • 配置全局 errorElement

练习 2:后台管理系统

目标:实现一个带侧边栏的后台管理系统。

要求

  • /admin 布局包含左侧导航(仪表盘、用户列表、设置)。
  • 访问 /admin 时默认展示仪表盘。
  • 用户列表页面使用 loader 从 API 加载数据。
  • 用户列表页面支持 "新增用户" 表单,使用 action 提交数据。
  • 用户列表页点击用户名进入 /admin/users/:id 详情页。

练习 3:全功能综合项目

目标:综合运用本教程所有知识点。

要求

  • 路由懒加载所有页面组件,添加 loading 动画。
  • /admin 路由配置鉴权守卫,未登录跳转到 /login
  • 为不同模块配置独立的 errorElement
  • 添加搜索功能,搜索参数体现在 URL 的 query string 中。
  • 提交表单后使用 state 传递结果到成功页。
  • 使用浏览器 Network 面板验证代码分割效果。

十五、练习题参考答案思路

练习 1 思路

  1. npm create vite@latest 创建项目,安装 react-router
  2. 创建 Home.jsxAbout.jsxNotFound.jsxErrorPage.jsx
  3. createBrowserRouter 配置四个路由。
  4. errorElement 挂在根路由。
  5. RouterProvider 挂载到根节点。

练习 2 思路

  1. 创建 AdminLayout.jsx,左侧导航用 NavLink,右侧放 <Outlet />
  2. router.jsx 中配置嵌套路由,index: true 指向 Dashboard
  3. 用户列表路由配置 loaderaction
  4. Users.jsx 中用 useLoaderData() 获取列表,用 <Form method="post"> 提交新增。
  5. 详情页路由 path: ':userId',用 useParams() 获取用户 ID。
  6. 详情页用 useNavigate(-1) 返回。

练习 3 思路

  1. 所有页面用 lazy: () => import(...) 懒加载。
  2. main.jsx 中用 <Suspense> 包裹 <RouterProvider>
  3. 创建 AuthGuard.jsx,包裹需要鉴权的路由组件。
  4. /admin/shop 分别设置 errorElement
  5. 搜索页用 useSearchParams() 读写 query string。
  6. 提交后用 navigate('/success', { state: { ... } }) 传递结果。
  7. 打开 Network 面板,观察懒加载 chunk 的加载时机。

总结

React Router V7 是 React 生态中最主流的路由方案,其核心概念如下:

  1. 路由表:用 createBrowserRouter 集中管理所有路由。
  2. 嵌套路由:用 children 声明层级关系,用 <Outlet /> 渲染子路由。
  3. 数据加载:用 loader 预加载数据,用 action 处理表单提交。
  4. 错误处理:用 errorElement 统一捕获路由层异常。
  5. 懒加载:用 lazy 属性按路由拆分代码包,提升首屏性能。

课后请完成三个练习,逐步从零搭建一个完整的 React Router V7 应用。

0
博主关闭了当前页面的评论