人人都会AI编程

第 12 章 路由管理:React Router v6

更新时间:2026-07-11

React Router 是 React 应用中最主流的路由解决方案,当前主版本为 v6.x。v6 相比 v5 进行了大量简化与增强,核心 API 更加直观,支持嵌套路由、数据加载、路由守卫等企业级需求。

12.1 路由基础配置与嵌套路由

安装与核心组件

npm install react-router-dom

React Router v6 的核心组件:

  • BrowserRouter:使用 HTML5 history API 的路由容器(开发常用)
  • HashRouter:使用 URL hash 的路由容器
  • Routes:路由匹配区域,替代 v5 的 Switch
  • Route:单个路由规则
  • Link / NavLink:声明式导航
  • Outlet:嵌套路由的子路由出口

基础路由配置:

import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

function Home() { return <h2>首页</h2>; }
function About() { return <h2>关于我们</h2>; }

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">首页</Link>
        <Link to="/about">关于</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </BrowserRouter>
  );
}

element 属性替代 v5 的 component / render,可以传入任意 JSX。

嵌套路由

在父路由组件中使用 Outlet 渲染匹配的子路由。这使得布局复用变得极其简单。

// 布局组件
function DashboardLayout() {
  return (
    <div>
      <h1>控制台</h1>
      <nav>
        <Link to="stats">统计</Link>
        <Link to="settings">设置</Link>
      </nav>
      <Outlet /> {/* 子路由内容将在此处渲染 */}
    </div>
  );
}

function Stats() { return <div>统计页面</div>; }
function Settings() { return <div>设置页面</div>; }

function App() {
  return (
    <BrowserRouter>
      <Routes>
        <Route path="/dashboard" element={<DashboardLayout />}>
          <Route index element={<Stats />} />        {/* index 路由:默认子路由 */}
          <Route path="stats" element={<Stats />} />
          <Route path="settings" element={<Settings />} />
        </Route>
        <Route path="/" element={<Home />} />
      </Routes>
    </BrowserRouter>
  );
}
  • index 路由:当匹配父路由路径时,自动渲染的默认子路由。
  • 子路由路径相对于父路由,无需写完整路径 /dashboard/stats

12.2 动态路由、路由参数、查询参数

动态路由与 useParams

定义带参数的路由,使用 :参数名 语法,通过 useParams() 获取参数值。

// 路由定义
<Route path="/users/:userId" element={<UserProfile />} />

// 组件内获取参数
import { useParams } from 'react-router-dom';

function UserProfile() {
  const { userId } = useParams();
  return <div>用户ID: {userId}</div>;
}

查询参数(Query String)

v6 不再默认解析查询参数,需使用 useSearchParamsURLSearchParams

import { useSearchParams } from 'react-router-dom';

function SearchPage() {
  const [searchParams, setSearchParams] = useSearchParams();
  const keyword = searchParams.get('q') || '';

  const updateKeyword = (newKeyword) => {
    setSearchParams({ q: newKeyword });
  };

  return (
    <div>
      <input value={keyword} onChange={e => updateKeyword(e.target.value)} />
      <p>搜索词:{keyword}</p>
    </div>
  );
}

setSearchParams 会更新 URL 查询参数并触发导航。

可选参数与通配符

v6 不直接支持可选参数,可用多路由或 useLocation 自行解析。通配符使用 * 匹配任意路径。

<Route path="/files/*" element={<FileViewer />} />

FileViewer 内通过 useParams()* 获取剩余路径,可用于文件系统式路由。

12.3 编程式导航与路由跳转

除了 <Link>,经常需要在事件处理、异步操作后跳转页面。使用 useNavigate hook。

import { useNavigate } from 'react-router-dom';

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

  const handleLogin = async () => {
    await loginAPI();
    navigate('/dashboard', { replace: true }); // 替换当前历史记录
  };

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

navigate 的第一个参数也是路由路径,第二参数可以传递状态:

navigate('/result', { state: { from: 'login' } });

目标组件通过 useLocation 读取:

import { useLocation } from 'react-router-dom';

function ResultPage() {
  const location = useLocation();
  const from = location.state?.from;
  return <div>来自:{from}</div>;
}

<Navigate> 组件:用于声明式的重定向,适合条件渲染。

import { Navigate } from 'react-router-dom';

function ProtectedPage({ isLoggedIn }) {
  if (!isLoggedIn) {
    return <Navigate to="/login" replace />;
  }
  return <Dashboard />;
}

12.4 路由守卫与权限控制实现

React Router 没有内置的路由守卫机制,但可以利用组件逻辑实现鉴权控制。常见方式:封装一个 <ProtectedRoute> 组件。

import { Navigate, useLocation } from 'react-router-dom';

function ProtectedRoute({ children }) {
  const isLoggedIn = useAuth(); // 自定义 hook 获取登录状态
  const location = useLocation();

  if (!isLoggedIn) {
    // 将当前路径传给登录页,登录后可跳回
    return <Navigate to="/login" state={{ from: location }} replace />;
  }
  return children;
}

在路由中使用:

<Routes>
  <Route path="/dashboard" element={
    <ProtectedRoute>
      <DashboardLayout />
    </ProtectedRoute>
  }>
    {/* 子路由 */}
  </Route>
  <Route path="/login" element={<Login />} />
</Routes>

对于基于角色的权限,可以在 ProtectedRoute 内校验角色:

function AdminRoute({ children }) {
  const { user } = useAuth();
  if (!user || user.role !== 'admin') {
    return <Navigate to="/403" replace />;
  }
  return children;
}

更复杂的权限可结合路由配置数组动态生成路由,避免重复书写保护组件。

12.5 路由懒加载与代码分割

对于大型应用,按路由拆分代码是提升首屏加载速度的关键。使用 React.lazy + Suspense 实现。

import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>加载中...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

Suspense 包裹 <Routes> 或单个懒加载组件,在动态导入过程中显示 fallback。v6 中 React.lazy 需要默认导出组件。

优化技巧:将懒加载粒度控制在路由级别,避免过多的 loading 闪烁。

12.6 数据路由:Loader / Action 数据加载与提交

React Router v6.4+ 引入了数据路由(Data Router),核心是 createBrowserRouterRouterProvider,提供路由级别的数据加载(Loader)和表单提交处理(Action),支持数据预获取、错误处理、乐观更新等。

创建数据路由

import { createBrowserRouter, RouterProvider } from 'react-router-dom';
import RootLayout from './layouts/Root';
import HomePage, { loader as homeLoader } from './pages/Home';

const router = createBrowserRouter([
  {
    path: '/',
    element: <RootLayout />,
    children: [
      { index: true, element: <HomePage />, loader: homeLoader },
      { path: 'products/:id', element: <ProductPage />, loader: productLoader },
    ],
  },
]);

function App() {
  return <RouterProvider router={router} />;
}

Loader:组件渲染前获取数据

每个路由可以定义一个 loader 函数,在路由匹配时并行调用(数据预加载)。组件通过 useLoaderData 获取数据。

// productLoader.js
export async function loader({ params }) {
  const res = await fetch(`/api/products/${params.id}`);
  return res.json();
}

// ProductPage.jsx
import { useLoaderData } from 'react-router-dom';

function ProductPage() {
  const product = useLoaderData();
  return <div>{product.name}</div>;
}

优势

  • 数据请求在路由跳转时即开始,无需等组件挂载再请求,体验更流畅。
  • 支持嵌套路由的并行数据加载。
  • 自动处理加载状态和错误(配合 errorElement)。

Action:处理表单提交等副作用

action 函数类似 loader,但用于处理 POST / PUT / DELETE 等请求。组件通过 <Form> 组件提交。

import { Form, useActionData } from 'react-router-dom';

// 路由定义
{
  path: 'login',
  element: <LoginPage />,
  action: async ({ request }) => {
    const formData = await request.formData();
    const user = await loginAPI(Object.fromEntries(formData));
    return redirect('/dashboard');
  },
}

// LoginPage.jsx
function LoginPage() {
  const actionData = useActionData();
  return (
    <Form method="post">
      <input name="username" />
      <input name="password" type="password" />
      <button type="submit">登录</button>
      {actionData?.error && <p>{actionData.error}</p>}
    </Form>
  );
}

<Form> 组件会阻止默认表单提交,调用路由的 actionuseActionData 获取 action 返回的数据(用于错误提示等)。

错误处理与悬停状态

可以给每个路由定义 errorElement,当 loader 或 action 抛出异常时自动渲染错误界面。

{
  path: 'products/:id',
  element: <ProductPage />,
  loader: productLoader,
  errorElement: <ErrorPage />,
}

还可以使用 useRouteError 获取错误详情。结合 Suspense 或内部 <Await> 处理异步组件不会冲突,因为数据路由本身不依赖 <Suspense>

传统路由 vs 数据路由的选择

  • 如果项目仅需基本页面跳转,传统 <BrowserRouter> + <Routes> 足够。
  • 如果涉及大量数据加载与表单处理、追求更好的性能和用户体验,推荐使用数据路由。

数据路由是现代 React Router 的主推方向,Next.js、Remix 等框架也借鉴了类似思想。


以上是 React Router v6 最核心、最实用的内容。掌握这些足以应对大多数业务场景,进一步优化可查阅官方文档了解 useFetcherdeferAwait 等高级特性。