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 />} />
1.4 路由跳转(Link 与 NavLink)
| 方法/组件名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 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 />}
/>
2.3 Link 与 NavLink
| 组件名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| 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" 标记激活项;className 和 style 支持函数形式接收 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 名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| useParams | const params = useParams(); | 获取当前路由中的动态参数对象 | 见下方代码 | 返回对象键名与路径中 :paramName 一致;所有值均为字符串类型;若参数未匹配,对应值为 undefined |
function UserProfile() {
const { userId } = useParams();
return <div>用户ID: {userId}</div>;
}
// 路由: <Route path="/user/:userId" element={<UserProfile />} />
3.4 路由状态与查询参数(useSearchParams)
| Hook 名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| useSearchParams | const [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 名称 | 语法 | 用途 | 代码示例 | 注意事项 |
|---|---|---|---|---|
| useNavigate | const 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/b,navigate("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> | 在懒加载组件或异步数据加载时显示 fallback | fallback 必须是 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 需在服务端匹配路由并渲染初始 HTML | BrowserRouter 依赖浏览器 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。