Skip to content
路由(React Router v6)

路由(React Router v6)

React Router 是 React 事实标准的路由库。v6 带来了显著 API 变化:Routes 取代 Switchelement prop 替代 children 写法、Hooks 优先。

官方文档:React Router 安装:npm install react-router-dom


基础概念

术语 说明
BrowserRouter 使用 HTML5 History API 的路由器
Routes 路由规则容器,匹配第一个相符的路由
Route 单条路由规则(路径 → 组件映射)
Outlet 嵌套路由的子路由渲染出口
Link / NavLink 声明式导航链接

快速开始

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

function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">首页</Link>
        <Link to="/about">关于</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/user/:id" element={<UserProfile />} />
        <Route path="*" element={<NotFound />} />
      </Routes>
    </BrowserRouter>
  );
}

⚠️ 与 v5 的关键区别:<Route> 的组件用 element prop(不是 children),<Routes> 取代了 <Switch>


路由参数

路径参数(:param)

// 定义
<Route path="/user/:id" element={<UserProfile />} />

// 使用
import { useParams } from 'react-router-dom';
function UserProfile() {
  const { id } = useParams();
}

查询字符串

// /search?q=react&page=2
import { useSearchParams } from 'react-router-dom';

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

  // 修改查询参数
  setSearchParams({ q: 'vue', page: 1 });
}

通配符(*)

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

// 在 FileViewer 中使用 useParams() 的 '*' 获取剩余路径
const { '*': restPath } = useParams();  // 如 "2024/report.pdf"

编程式导航

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

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

  const handleLogin = async () => {
    await loginAPI();
    navigate('/dashboard');         // 前进
    navigate(-1);                   // 后退
    navigate('/result', { state: { from: 'login' } });  // 携带 state
    navigate('/target', { replace: true });  // 替换当前历史记录
  };
}

读取路由 State

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

function Result() {
  const location = useLocation();
  const { from } = location.state || {};  // navigate 时传递的 state
}

🚨 陷阱:通过 state 传递的数据在页面刷新后会丢失(它存在于浏览器内存的 history 中,不持久化)。需要持久化时用 query 参数或 localStorage。


嵌套路由

// 父组件(Layout)
function Layout() {
  return (
    <div>
      <Header />
      <Outlet />  {/* 子路由在此渲染 */}
      <Footer />
    </div>
  );
}

// 路由配置
<Routes>
  <Route path="/" element={<Layout />}>
    <Route index element={<Home />} />           {/* index = path="/" */}
    <Route path="products" element={<Products />}>
      <Route path=":id" element={<ProductDetail />} />
    </Route>
    <Route path="about" element={<About />} />
  </Route>
</Routes>
渲染路径 组件
/ Layout → Home
/products Layout → Products
/products/123 Layout → Products → ProductDetail

Link vs NavLink

<Link to="/" replace>首页</Link>

// NavLink 提供激活状态
<NavLink
  to="/dashboard"
  className={({ isActive, isPending }) =>
    isActive ? 'active' : isPending ? 'pending' : ''
  }
  end               // 精确匹配(/dashboard 不匹配 /dashboard/settings
>
  仪表盘
</NavLink>

路由守卫

function ProtectedRoute({ children }) {
  const [user] = useAuth(); // 自定义认证 hook

  if (!user) {
    return <Navigate to="/login" replace />;
  }
  return children;
}

// 使用
<Route path="/admin" element={
  <ProtectedRoute>
    <AdminPanel />
  </ProtectedRoute>
} />

💡 最佳实践:路由守卫作为布局路由(Layout Route)更简洁:<Route element={<ProtectedRoute />}>,将其包裹的所有子路由自动受保护。


Lazy Loading(路由级别的代码分割)

import { lazy, Suspense } from 'react';

const Dashboard = lazy(() => import('./pages/Dashboard'));
const Settings = lazy(() => import('./pages/Settings'));

function App() {
  return (
    <Suspense fallback={<Loading />}>
      <Routes>
        <Route path="/dashboard" element={<Dashboard />} />
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </Suspense>
  );
}

v6 Data Router(v6.4+)

React Router v6.4 引入了 Data Router,将路由配置与数据加载/变更绑定:

import { createBrowserRouter, RouterProvider } from 'react-router-dom';

const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    errorElement: <ErrorPage />,
    children: [
      { index: true, element: <Home /> },
      {
        path: 'products/:id',
        loader: async ({ params }) => {
          return fetch(`/api/products/${params.id}`);
        },
        element: <ProductDetail />,
      },
      {
        path: 'submit',
        action: async ({ request }) => {
          const data = await request.formData();
          return submitForm(data);
        },
      },
    ],
  },
]);

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

// 使用 loader 数据
function ProductDetail() {
  const data = useLoaderData();
}

Data Router 的 loader/action 优势

特性 说明
在渲染前加载数据 loader 在路由匹配后、组件渲染前执行
并行加载 多个嵌套路由的 loader 并行执行
错误处理 errorElement 自动捕获 loader 错误
表单处理 <Form> 组件自动触发 action

常用 Hooks 速查

Hook 返回 用途
useParams() { id: '123' } 路径参数
useSearchParams() [URLSearchParams, setter] 查询字符串
useNavigate() 函数 编程式导航
useLocation() { pathname, search, state } 当前路由信息
useLoaderData() loader 返回的数据 Data Router
useRouteError() errorElement 错误 错误处理
useMatches() 匹配的路由链 面包屑等

路由配置 vs 组件声明

// 方式 1:JSX 声明式(组件中)
<Routes>
  <Route path="/" element={<Home />} />
</Routes>

// 方式 2:配置式(createBrowserRouter + RouterProvider)
const router = createBrowserRouter([
  { path: '/', element: <Home /> }
]);

💡 最佳实践:中小型项目用声明式(直观),中大型项目用配置式(loader/action 更清晰地组织数据流)。


🚨 常见陷阱

  1. BrowserRouter 忘记包裹 — 所有路由组件必须包裹在该组件内
  2. 嵌套路由缺少 Outlet — 子路由不会自动渲染;缺少 <Outlet /> 子组件不可见
  3. path 写错 — v6 中路径默认相对,不以 / 开头是相对父路由;* 通配符必须是最后一个路由
  4. index vs path="/" — 索引路由用 index,显式 / 在某些嵌套场景行为不一致
  5. state 在刷新后丢失 — 通过 navigate('/a', { state: {...} }) 传递的数据刷新即无
  6. 在 v6 中使用 v5 组件<Switch><Redirect>useHistory 等已被移除