Article

路由管理 React Router DOM

更新于:2026-07-10

React Router DOM 路由管理

第一章:基础概念与快速上手

1.1 什么是 React Router DOM

概念名称说明注意事项
React Router DOM是 React 官方推荐的用于在 Web 应用中实现客户端路由的库,允许用户在不刷新页面的情况下切换视图。仅适用于 Web 环境;React Native 应使用 react-router-native。
客户端路由(Client-side Routing)由前端 JavaScript 控制 URL 变化和页面渲染,而非向服务器请求新页面。需配合服务器配置(如 SPA 回退规则),否则直接访问非根路径会返回 404。
声明式路由使用组件(如 <Route><Link>)声明路由行为,而非手动操作 history 对象。与命令式导航(如 useNavigate())互补,应根据场景选择。

1.2 安装与项目初始化

步骤名称操作细节注意事项
创建 React 项目使用 Create React App:npx create-react-app my-app确保 Node.js ≥ 18,npm ≥ 8
安装 react-router-dom在项目根目录执行:npm install react-router-dom当前主流版本为 v6.x,v5 语法不兼容
验证安装查看 package.json 中是否包含 "react-router-dom": "^6.x.x"若需 TypeScript 支持,无需额外安装类型包(已内置)

1.3 基本路由配置(BrowserRouter、Routes、Route)

方法/组件名称语法用途代码示例注意事项
BrowserRouter<BrowserRouter>{children}</BrowserRouter>提供 HTML5 History API 路由上下文见下方代码必须包裹所有路由相关组件;不支持 IE9 及以下
Routes<Routes>{children}</Routes>渲染第一个匹配的 Route(替代 v5 的 Switch)见下方代码子元素必须是 <Route><React.Fragment>
Route<Route path="..." element={...} />定义路径与组件的映射关系见下方代码element 必须是 React 元素(如 <Component />),不能是组件类或函数

BrowserRouter 示例:

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

function App() {
  return (
    <BrowserRouter>
      <AppRoutes />
    </BrowserRouter>
  );
}

Routes + Route 示例:

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

function AppRoutes() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
    </Routes>
  );
}

Route 基本用法:

<Route path="/users" element={<UserList />} />
方法/组件名称语法用途代码示例注意事项
Link<Link to="...">...</Link>声明式导航,点击后跳转到指定路径见下方代码不会刷新页面;等效于 <a> 但由 React Router 拦截
NavLink<NavLink to="..." [className][style]>{...}类似 Link,但可自动应用”激活”状态样式见下方代码通过 isActive 判断激活状态

Link 示例:

<Link to="/about">关于我们</Link>

NavLink 示例:

<NavLink
  to="/dashboard"
  className={({ isActive }) => isActive ? 'active' : ''}
>
  仪表盘
</NavLink>

第二章:核心组件详解

2.1 BrowserRouter 与 HashRouter

组件名称语法用途代码示例注意事项
BrowserRouter<BrowserRouter basename? future? window? >{children}</BrowserRouter>使用 HTML5 History API 实现干净 URL(如 /about见下方代码需服务器支持 SPA 回退(所有路径返回 index.html);不兼容 IE9 及以下
HashRouter<HashRouter basename? future? window? >{children}</HashRouter>使用 URL hash(如 #/about)实现路由,无需服务器配置见下方代码适用于静态托管(如 GitHub Pages);URL 不够美观;SEO 友好性较差

参数说明:

  • basename:所有路径的公共前缀(如 /app → 实际路径为 /app/home
  • future:启用未来版本行为(v6.x 中可选)
  • window:指定使用哪个 window 对象(用于 iframe 等场景)

BrowserRouter 示例:

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

function App() {
  return (
    <BrowserRouter basename="/app">
      <Routes>
        <Route path="/home" element={<Home />} />
      </Routes>
    </BrowserRouter>
  );
}

HashRouter 示例:

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

function App() {
  return (
    <HashRouter>
      <Routes>
        <Route path="/settings" element={<Settings />} />
      </Routes>
    </HashRouter>
  );
}

2.2 Routes 与 Route

组件名称语法用途代码示例注意事项
Routes<Routes>{children}</Routes>渲染第一个匹配的子 Route(互斥匹配)见下方代码替代 v5 的 <Switch>;必须直接包含 <Route><Fragment>
Route<Route path? index? element? loader? action? errorElement? children? />声明路径与 UI 的映射关系见下方代码path 支持动态段(:id)、通配符(*);index 表示父路径的默认子路由;element 必须是 React 元素(非组件引用)

关键属性说明:

  • index:当父路由匹配且无子路径时激活(如 /parent 匹配 <Route index element={<Default />} />
  • children:用于嵌套路由(见 2.4 Outlet)
  • errorElement:仅在数据加载出错时渲染(需配合 loader/action)
<Routes>
  <Route path="/" element={<Home />} />
  <Route path="*" element={<NotFound />} />
</Routes>
<Route
  path="/profile/:id"
  element={<Profile />}
/>
组件名称语法用途代码示例注意事项
Link<Link to={string | object} replace? state? reloadDocument? >{children}</Link>声明式导航到新路径见下方代码to 可为字符串或对象 { pathname, search, hash, state }replace 为 true 时替换当前历史记录;state 可传递临时数据(通过 useLocation().state 获取)
NavLink<NavLink to={...} end? caseSensitive? className? style? children? >{...}</NavLink>带激活状态的 Link,常用于导航菜单见下方代码end:仅当完整匹配路径时才激活(避免 /messages 激活 /messages/1);默认通过 aria-current="page" 标记激活项;classNamestyle 支持函数形式接收 isActive

Link 示例:

<Link to="/dashboard" state={{ from: "/login" }}>
  进入控制台
</Link>

NavLink 示例:

<NavLink
  to="/messages"
  end
  className={({ isActive }) => isActive ? "nav-active" : "nav-inactive"}
>
  消息
</NavLink>

2.4 Outlet(嵌套路由出口)

重要: 嵌套的子 Route 指向的页面,会渲染到父 Route 的 <Outlet/> 位置,否则,子 Route 指向的页面不渲染。

组件名称语法用途代码示例注意事项
Outlet<Outlet context? />在父路由组件中渲染匹配的子路由内容见下方代码必须在父路由组件中使用;子路由通过 <Route> 嵌套定义;context 可向子路由传递数据(配合 useOutletContext()

父组件 Layout.js:

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

function Layout() {
  return (
    <div>
      <header>顶部导航</header>
      <main>
        <Outlet />
      </main>
    </div>
  );
}

路由配置:

<Route path="/" element={<Layout />}>
  <Route index element={<Home />} />
  <Route path="about" element={<About />} />
</Route>

2.5 Navigate(编程式导航)

组件/方法名称语法用途代码示例注意事项
Navigate(组件)<Navigate to={string | object} replace? state? />在 JSX 中立即跳转(常用于重定向)见下方代码渲染即触发跳转;replace 控制是否替换历史记录;不能用于条件渲染之外的逻辑(如 useEffect)
useNavigate(Hook)const navigate = useNavigate(); navigate(to, { replace?, state? });在事件或副作用中执行导航见下方代码仅可在函数组件或自定义 Hook 中调用;to 支持相对路径(如 "..");返回函数可用于取消导航(v6.4+)

注意: Navigate 组件适用于声明式重定向(如权限拦截),useNavigate 适用于交互式导航(如按钮点击)。

Navigate 示例:

function ProtectedRoute({ children }) {
  const auth = useAuth();
  if (!auth.user) {
    return <Navigate to="/login" replace />;
  }
  return children;
}

useNavigate 示例:

function LogoutButton() {
  const navigate = useNavigate();
  const handleLogout = () => {
    logout();
    navigate("/login", { replace: true });
  };
  return <button onClick={handleLogout}>退出</button>;
}

第三章:高级路由功能

3.1 嵌套路由(Nested Routes)

概念/操作名称说明注意事项
嵌套路由定义在父 <Route> 中嵌套子 <Route>,形成层级路径结构(如 /dashboard/settings父路由必须使用 element 渲染包含 <Outlet> 的组件
路由配置方式子路由通过 children 属性或 JSX 嵌套定义推荐使用 JSX 嵌套(更直观)

嵌套路由配置示例:

<Route path="/parent" element={<ParentLayout />}>
  <Route index element={<Default />} />
  <Route path="child" element={<Child />} />
</Route>

完整示例:

// App.jsx
<Routes>
  <Route path="/admin" element={<AdminLayout />}>
    <Route index element={<Dashboard />} />
    <Route path="users" element={<UserList />} />
  </Route>
</Routes>

// AdminLayout.jsx
function AdminLayout() {
  return (
    <div>
      <nav>管理侧边栏</nav>
      <main><Outlet /></main>
    </div>
  );
}

注意: 父路径 /admin 匹配时渲染 AdminLayout;子路径 /admin/users 渲染 UserList 到 <Outlet />;index 路由匹配 /admin 本身,缺失的话,访问 /admin 路由页面可能为空。

3.2 动态路由(Dynamic Segments)

概念/操作名称说明注意事项
动态段(Dynamic Segment)路径中以 :paramName 形式定义的变量部分(如 /users/:id参数名必须是合法 JavaScript 标识符
通配符(Splat)使用 * 匹配任意剩余路径(如 /files/*通常用于嵌套路由或文件系统模拟

动态路径定义:

<Route path="/items/:itemId" element={<ItemDetail />} />

多动态段:

<Route path="/products/:category/:id" element={<Product />} />

注意: v6 不支持正则表达式;参数值始终为字符串。

通配符路径:

// 匹配 /docs/intro, /docs/api/auth 等
<Route path="/docs/*" element={<DocsLayout />} />

useParams() 中通过 * 获取剩余路径(如 "api/auth")。

3.3 路由参数获取(useParams)

Hook 名称语法用途代码示例注意事项
useParamsconst params = useParams();获取当前路由中的动态参数对象见下方代码返回对象键名与路径中 :paramName 一致;所有值均为字符串类型;若参数未匹配,对应值为 undefined
function UserProfile() {
  const { userId } = useParams();
  return <div>用户ID: {userId}</div>;
}
// 路由: <Route path="/user/:userId" element={<UserProfile />} />

3.4 路由状态与查询参数(useSearchParams)

Hook 名称语法用途代码示例注意事项
useSearchParamsconst [searchParams, setSearchParams] = useSearchParams();读写 URL 查询字符串(?key=value见下方代码
function ProductList() {
  const [searchParams, setSearchParams] = useSearchParams();
  const category = searchParams.get("category");
  return <div>Category: {category}</div>;
}

3.5 编程式导航(useNavigate)

Hook 名称语法用途代码示例注意事项
useNavigateconst navigate = useNavigate(); navigate(to, { replace?, state?, preventScrollReset? });在代码中触发导航(非点击 Link)见下方代码to 可为绝对路径(/home)、相对路径(..../sibling);replace: true 替换当前历史记录;state 数据可通过 useLocation().state 获取;不能在 useEffect 外直接调用(需在事件或异步回调中)
function DeleteButton({ id }) {
  const navigate = useNavigate();
  const handleDelete = async () => {
    await api.delete(id);
    navigate("/items", { replace: true, state: { message: "删除成功" } });
  };
  return <button onClick={handleDelete}>删除</button>;
}

相对路径规则:

  • ..:返回上一级路径(类似 cd ..
  • ../sibling:跳转到同级路由
  • 无前缀:相对于当前路由追加(如当前 /a/bnavigate("c")/a/b/c

第四章:路由守卫与加载优化

4.1 路由守卫(自定义保护逻辑)

操作名称操作细节注意事项
创建认证上下文使用 useContext + useState 管理登录状态避免在每次渲染时重建 context 值
封装受保护路由组件在组件中判断权限,决定渲染内容或重定向必须在 <Routes> 内部使用 <Route element={...}> 包裹

useAuth(自定义 Hook):

// auth-context.js
import { createContext, useContext, useState } from 'react';

const AuthContext = createContext();

export function AuthProvider({ children }) {
  const [user, setUser] = useState(null);
  const login = (userData) => setUser(userData);
  const logout = () => setUser(null);
  return (
    <AuthContext.Provider value={{ user, login, logout }}>
      {children}
    </AuthContext.Provider>
  );
}

export const useAuth = () => useContext(AuthContext);

注意: 需在应用根部包裹 <AuthProvider>;初始 user 可从 localStorage 恢复。

ProtectedRoute(自定义组件):

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

路由配置:

<Route
  path="/dashboard"
  element={
    <ProtectedRoute>
      <Dashboard />
    </ProtectedRoute>
  }
/>

注意: 必须配合 useAuth() 等状态管理;使用 <Navigate replace /> 避免后退循环;可扩展支持角色权限(如 role === 'admin')。

4.2 加载中状态与错误边界

概念名称说明注意事项
加载状态(Loading State)在数据获取期间显示占位 UI(如骨架屏)避免白屏,提升用户体验
错误边界(Error Boundary)捕获子组件渲染或生命周期中的 JavaScript 错误React Router v6 不自动提供错误边界,需手动实现

自定义 ErrorBoundary:

class ErrorBoundary extends Component {
  constructor(props) {
    super(props);
    this.state = { hasError: false };
  }

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

  render() {
    if (this.state.hasError) {
      return <div>页面加载出错</div>;
    }
    return this.props.children;
  }
}

路由中使用 ErrorBoundary:

<Route
  path="/profile"
  element={
    <ErrorBoundary>
      <Profile />
    </ErrorBoundary>
  }
/>

注意: 仅捕获子组件错误,不捕获事件处理函数;需为类组件(Hooks 无法实现);可结合 resetError 按钮恢复。

Suspense(配合 lazy):

<Suspense fallback={<div>加载中...</div>}>
  <UserProfile />
</Suspense>
组件/方法名称语法用途注意事项
Suspense<Suspense fallback={<Spinner />}>{children}</Suspense>在懒加载组件或异步数据加载时显示 fallbackfallback 必须是 React 元素;可嵌套多层 Suspense;不能捕获 Promise reject(需配合 errorElement)

注意: React Router v6.4+ 引入了 errorElement 和 loader,可在 Route 级别处理数据加载错误,但本节聚焦通用方案。

4.3 懒加载路由(React.lazy + Suspense)

操作名称操作细节注意事项
使用 React.lazy 包装组件const LazyComponent = React.lazy(() => import('./Component'));仅支持默认导出(default export)
用 Suspense 包裹懒加载组件在路由层级或 App 根部添加 <Suspense fallback={...}>fallback 应轻量,避免自身懒加载

React.lazy 完整示例:

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

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>加载页面...</div>}>
        <Routes>
          <Route path="/dash" element={<Dashboard />} />
          <Route path="/settings" element={<Settings />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

注意: 必须与 Suspense 配合使用;不支持命名导出(需调整为 default 或中间包装);生产构建会自动生成分块文件(如 dashboard.chunk.js)。

自定义 fallback 组件:

function PageSpinner() {
  return <div className="spinner">页面加载中...</div>;
}

// 使用
<Suspense fallback={<PageSpinner />}>
  <Routes>{/* ... */}</Routes>
</Suspense>

注意: fallback 组件不应包含异步逻辑;可全局复用。

最佳实践:

  • 对非首屏路由(如 /admin, /profile)启用懒加载
  • 首屏关键路径(如 /home)建议预加载或不懒加载
  • 结合 Webpack 魔法注释自定义 chunk 名称:
React.lazy(() => import(/* webpackChunkName: "dashboard" */ './Dashboard'))

第五章:最佳实践与常见问题

5.1 路由结构组织建议

概念/操作名称说明注意事项
路由集中式配置将所有 <Route> 定义在单一文件(如 routes.js)中适用于中小型项目;大型项目可模块化拆分
模块化路由拆分按功能域(如 authRoutes, adminRoutes)组织路由需配合 ... 展开或 <Route> 嵌套
布局组件复用使用含 <Outlet> 的布局组件包裹子路由避免重复编写导航、侧边栏等公共 UI

集中式路由配置:

// routes.jsx
import { Route, Routes } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';

export default function AppRoutes() {
  return (
    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
    </Routes>
  );
}

在 App.jsx 中直接引入 <AppRoutes />。保持路径扁平化,避免深层嵌套。

布局 + 嵌套路由:

<Route path="/app" element={<AppLayout />}>
  <Route index element={<Dashboard />} />
  <Route path="settings" element={<Settings />} />
</Route>

AppLayout 内含 <Outlet /> 渲染子页面;父路径 /app 不应直接渲染业务内容。

路由懒加载整合:

const Dashboard = React.lazy(() => import('./pages/Dashboard'));
<Route path="/dash" element={<Dashboard />} />

所有懒加载路由需被同一 <Suspense> 包裹。

5.2 404 页面处理

操作名称操作细节注意事项
定义通配符路由使用 <Route path="*" element={<NotFound />} />必须放在 <Routes> 最后
自定义 404 组件创建独立 NotFound 页面组件可包含返回首页链接或搜索框

通配符路由(*):

<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/about" element={<About />} />
  <Route path="*" element={<NotFound />} />
</Routes>

注意: 必须是最后一个 <Route>;不能与其他路径共存于同一层级匹配。

NotFound 组件:

function NotFound() {
  return (
    <div>
      <h1>404 - 页面未找到</h1>
      <Link to="/">返回首页</Link>
    </div>
  );
}

可加入品牌 Logo 或联系支持信息;避免复杂交互,聚焦引导回有效路径。

5.3 路由滚动行为控制

概念名称说明注意事项
默认滚动行为React Router v6 默认不自动滚动到顶部用户可能停留在上一页滚动位置
自定义滚动复位在路由切换时调用 window.scrollTo(0, 0)需监听 location 变化

ScrollToTop 组件:

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

export default function ScrollToTop() {
  const { pathname } = useLocation();
  useEffect(() => {
    window.scrollTo(0, 0);
  }, [pathname]);
  return null;
}

App.jsx 中使用:

<BrowserRouter>
  <ScrollToTop />
  <Routes>{/* ... */}</Routes>
</BrowserRouter>

注意: 必须放在 <Routes> 同级或父级;仅适用于浏览器环境(SSR 需判断 typeof window !== 'undefined')。

useLocation Hook:

const location = useLocation(); — 获取当前 URL 信息(用于监听变化)。通常与 useEffect 配合使用。返回对象包含 pathname, search, hash, state

进阶场景:

  • 若使用锚点链接(/page#section),应保留原生滚动行为
  • 可通过 location.key 判断是否为新导航(而非参数变化)

5.4 服务端渲染(SSR)兼容性说明

概念名称说明注意事项
SSR 与客户端路由差异SSR 需在服务端匹配路由并渲染初始 HTMLBrowserRouter 依赖浏览器 history,不能在服务端使用
静态路由配置必要性SSR 要求路由结构可静态分析(非动态生成)避免在 useEffect 中动态添加路由

StaticRouter(服务端):

import { StaticRouter } from 'react-router-dom/server';

const html = renderToString(
  <StaticRouter location={req.url}>
    <App />
  </StaticRouter>
);

通常用于 Express / Next.js 自定义 SSR;仅用于服务端;location 为请求的完整路径(如 /about?tab=info)。

客户端激活(Hydration):

// client-entry.js
const root = ReactDOM.hydrateRoot(
  document.getElementById('root'),
  <BrowserRouter>
    <App />
  </BrowserRouter>
);

服务端与客户端路由结构必须一致;避免在组件中使用 window 等浏览器 API(除非条件判断)。

数据预取(Data Hydration):

<!-- 注入 -->
<script>
  window.__INITIAL_DATA__ = {{ serializedData }};
</script>

React Router v6.4+ 的 loader 支持 SSR 数据流,但需框架集成(如 Remix)。

重要提示: Create React App 默认不支持 SSR,需使用 Next.js、Remix 或自建 SSR 服务。若仅需 SEO 友好,可考虑预渲染(Prerendering)替代完整 SSR。