七、React Router
React Router 官方文档 - React Router 路由库

目录
[七、React Router](#七、React Router)
[1.1. 安装 React Router](#1.1. 安装 React Router)
[1.2. 基本路由配置与使用](#1.2. 基本路由配置与使用)
[1.3.BrowserRouter 与 Routes](#1.3.BrowserRouter 与 Routes)
[2.3.声明式 vs 编程式](#2.3.声明式 vs 编程式)
[3.1. 动态路由参数 (params)](#3.1. 动态路由参数 (params))
[3.2. 查询字符串参数 (searchParams)](#3.2. 查询字符串参数 (searchParams))
[3.3. 路由状态 (state)](#3.3. 路由状态 (state))
[4.1. 基本嵌套路由](#4.1. 基本嵌套路由)
[4.2. 索引路由 (index 父路由)](#4.2. 索引路由 (index 父路由))
[4.3.Outlet 与 索引路由](#4.3.Outlet 与 索引路由)
[4.5.Navigate vs useNavigate Hook](#4.5.Navigate vs useNavigate Hook)
[5.1. 数据加载 (loader 与 action)](#5.1. 数据加载 (loader 与 action))
[5.2. 两种路由模式的选择](#5.2. 两种路由模式的选择)
[5.3. RouterProvider 与 数据路由](#5.3. RouterProvider 与 数据路由)
[6. React Router 核心流程全景图](#6. React Router 核心流程全景图)
[1. 团队成员管理](#1. 团队成员管理)
[2. 键盘事件可视化](#2. 键盘事件可视化)
[3. 鼠标事件追踪器](#3. 鼠标事件追踪器)
|-----------------|--------------------------------------|-----------------------------------------------|--------------------------------------------------------|
| 核心角色 | 一句话大白话解释 | 日常类比 | 主要用途 |
| Router | 应用的导航引擎,为所有路由组件提供上下文环境csdn.net+1 | 汽车导航系统 的主机 | 整个应用的最外层容器,必须用它包裹其他路由相关组件 |
| Routes | 智能路由匹配器,根据当前URL决定显示哪个组件 | 红绿灯路口 ,根据指令(URL)放行对应车辆(组件) | 包裹Route,负责匹配当前URL并只渲染匹配的第一个组件 |
| Route | 一条具体的导航规则,定义"哪个路径显示哪个组件" | 路标 ,指示"去图书馆 走这条路,显示图书馆的界面" | 定义path(路径)和element(组件)的对应关系 |
| Link | 不会触发页面刷新的导航链接 | 地图软件 里的"开始导航"按钮,点击后只切换地图显示,不重置整个APP | 用于声明式导航,渲染为<a>标签,点击后改变URL |
| useNavigate | 允许你在代码中"命令"跳转的函数 | 汽车 的方向盘 ,你可以随时指令它转向(跳转)任何地方 | 用于编程式导航,在事件处理、条件判断后进行跳转 |
| Outlet | 父路由组件中用于展示子路由内容的占位符 | 地铁站 的候车台 ,不同线路(子路由) 的乘客(组件)会在这里候车 | 在嵌套路由中,父组件必须使用Outlet来渲染匹配的子组件 |
| loader | 组件渲染前 自动执行的数据加载函数 | 提前 把行李(数据) 装车,等人(组件)到齐后直接走 | 在路由配置时定义,在路由组件渲染前异步获取数据,并将数据通过useLoaderData()传递给组件 |
| action | 处理表单提交等写操作数据的函数 | 快递员 ,负责投递包裹(提交数据) 到指定地点 | 用于处理<form>提交等写操作,与loader配合处理数据流 |
1.快速上手:安装与基本配置
1.1. 安装 React Router
首先,需要将React Router安装到的React项目中。在项目根目录下运行:
bash
npm install react-router-dom
1.2. 基本路由配置与使用
在React Router v6中,使用BrowserRouter作为路由容器,Routes和Route来定义路由规则。
javascript
// src/App.jsx
import React from 'react';
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';
// 简单的页面组件
function Home() { return <h1>欢迎来到主页!</h1>; }
function About() { return <h1>这是关于页面。</h1>; }
function Contact() { return <h1> 联系页面。</h1>; }
function App() {
return (
<BrowserRouter>
<div>
{/* 导航菜单 */}
<nav>
<ul>
<li><Link to="/">主页</Link></li>
<li><Link to="/about">关于</Link></li>
<li><Link to="/contact">联系</Link></li>
</ul>
</nav>
{/* 路由配置 */}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</div>
</BrowserRouter>
);
}
export default App;
1.3.BrowserRouter 与 Routes
BrowserRouter
它利用HTML5的history API(如pushState)来保持UI和URL的同步。必须用它包裹的应用,否则路由功能无法工作。
Routes 和 Route
它们是路由的"匹配器"和"规则定义者"。
Routes
类似一个智能的<Switch>(v5的概念),它会遍历其所有子Route,只渲染第一个与当前URL匹配的Route组件。这避免了同时渲染多个组件的问题。
Route
定义具体的路由规则。path属性指定URL路径模式,element属性指定该路径匹配时要渲染的React组件。
Link
创建一个导航链接。它最终渲染为一个<a>标签,但点击它不会刷新页面,而是改变URL,React Router会自动根据新的URL匹配对应的Route并更新UI。
2.导航
导航是应用的核心,React Router提供了两种主要方式。
2.1.声明式导航:<Link> & <NavLink>
就像在地图上设置一个"目的地",点击后自动规划路线。
javascript
import { Link, NavLink } from 'react-router-dom';
function Navigation() {
return (
<nav>
{/* Link 基础用法 */}
<Link to="/about">关于</Link>
{/* NavLink 带有激活状态样式 */}
<NavLink
to="/about"
className={({ isActive }) => isActive ? 'active-link' : ''}
>
关于
</NavLink>
</nav>
);
}
NavLink 是Link的增强版,它可以自动添加active类名(或指定的类名)到当前匹配的链接上,非常适合制作高亮显示当前页面的导航菜单。
2.2.编程式导航:useNavigate Hook
就像亲自握住方向盘,随时可以根据情况(如登录成功、表单提交完成)指令转向任何目的地。
javascript
import { useNavigate } from 'react-router-dom';
function LoginButton() {
const navigate = useNavigate();
const handleLogin = () => {
// 模拟登录逻辑...
// 登录成功后,跳转到首页
navigate('/dashboard');
};
return <button onClick={handleLogin}>登录</button>;
}
useNavigate 返回一个函数,调用该函数并传入目标路径即可实现跳转。它还支持:
后退/前进:navigate(-1) / navigate(1)
替换当前历史记录:navigate('/new-path', { replace: true }) ------ 用户无法通过后退按钮返回之前的页面。
传递状态:navigate('/target', { state: { from: 'current-page' } }) ------ 在目标页面通过useLocation().state获取,数据不暴露在URL中。
2.3.声明式 vs 编程式导航
|----------|--------------------------------|---------------------------------------|
| 特性 | 声明式导航 ( Link ) | 编程式导航 ( useNavigate ) |
| 本质 | 描述 "要去哪里 " | 命令 "现在就去哪里 " |
| 触发方式 | 用户点击链接 | 代码逻辑 中调用(如事件处理、条件判断) |
| 常见场景 | 页面菜单、导航栏 | 登录/登出、表单提交、异步操作后跳转 |
| 类比 | 选择目的地 的菜单 | 直接踩下油门 的踏板 |
3.路由参数:在页面间传递信息
就像快递包裹,需要在URL上写上"收件人地址"(参数),对方才能正确接收信息(数据)。
3.1. 动态路由参数 (params)
定义:在路由路径中使用:前缀定义参数片段,如/user/:id。
接收:在目标组件中使用useParams() Hook获取参数对象。
javascript
// 路由配置
<Route path="/users/:userId" element={<UserProfile />} />
// UserProfile.jsx
import { useParams } from 'react-router-dom';
function UserProfile() {
const { userId } = useParams(); // 解构获取 userId 参数
return <h1>用户ID: {userId}</h1>;
}
URL示例:
访问/users/123,userId的值就是'123'。
注意:参数始终是字符串类型。
3.2. 查询字符串参数 (searchParams)
定义:使用?分隔,&连接多个参数,如/search?query=react&page=1。
接收:使用useSearchParams() Hook获取一个类似URLSearchParams的对象。
javascript
import { useSearchParams } from 'react-router-dom';
function SearchResults() {
const [searchParams] = useSearchParams();
const query = searchParams.get('query'); // 获取 query 参数
const page = searchParams.get('page'); // 获取 page 参数
return <div>搜索 "{query}" 的第 {page} 页结果</div>;
}
URL示例:
访问/search?query=react&page=2,query为'react',page为'2'。
3.3. 路由状态 (state)
定义:通过navigate或Link的state属性传递数据,数据不会出现在URL中。
接收:在目标组件中使用useLocation() Hook获取location.state。
javascript
// 发送方
import { useNavigate } from 'react-router-dom';
function UserList() {
const navigate = useNavigate();
const users = [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}];
return (
<ul>
{users.map(user => (
<li key={user.id} onClick={() => navigate(`/user/${user.id}`, { state: { user } })}>
{user.name}
</li>
))}
</ul>
);
}
// 接收方
import { useLocation } from 'react-router-dom';
function UserProfile() {
const { state } = useLocation();
const user = state?.user; // 获取传递过来的用户对象
return <h1>用户名: {user?.name}</h1>;
}
优点:
适合传递对象数据或敏感信息,不会暴露在URL中,刷新页面后数据会丢失(除非用其他方式持久化)。
3.4.参数传递方式对比
|------------------|--------------------------|------------------------------|------------------------------------------|
| 参数类型 | URL 可见性 | 适用场景 | 示例 |
| params | 可见 (/user/123) | 标识性资源 ,如用户ID、文章ID | path="/user/:userId" |
| searchParams | 可见 (/search?q=react) | 筛选、分页、排序 等非核心标识的参数 | useSearchParams().get('q') |
| state | 不可见 | 传递复杂对象或临时状态 ,如表单提交成功后的数据 | navigate('/target', { state: { data } }) |
4.嵌套路由:构建复杂的页面结构
嵌套路由允许定义具有层级结构的路由,就像一个多层嵌套的文件夹,父文件夹可以包含子文件夹。
4.1. 基本嵌套路由
在父路由组件中使用Outlet组件作为子路由内容的占位符。
在父路由的element中配置子路由的Route。
javascript
// App.jsx
<Routes>
<Route path="/admin" element={<AdminLayout />}>
{/* 嵌套在 /admin 下的子路由 */}
<Route path="users" element={<UserList />} />
<Route path="settings" element={<AdminSettings />} />
</Route>
</Routes>
// AdminLayout.jsx (父路由组件)
import { Outlet } from 'react-router-dom';
function AdminLayout() {
return (
<div>
<h1>管理后台</h1>
<nav>
{/* 子路由导航链接 */}
<Link to="users">用户管理</Link>
<Link to="settings">系统设置</Link>
</nav>
{/* 子路由内容将渲染在这里 */}
<Outlet />
</div>
);
}
当访问/admin/users时,AdminLayout组件会渲染,其中的<Outlet />会被UserList组件替换。
4.2. 索引路由 (index 父路由)
定义:当用户访问父路由路径(如/admin)但未指定子路由时,默认渲染的子路由。在子路由上添加index属性。
javascript
<Routes>
<Route path="/admin" element={<AdminLayout />}>
{/* 访问 /admin 时默认渲染的子路由 */}
<Route index element={<Dashboard />} />
<Route path="users" element={<UserList />} />
<Route path="settings" element={<AdminSettings />} />
</Route>
</Routes>
访问/admin会显示Dashboard组件;
访问/admin/users显示UserList组件。
4.3.Outlet 与 索引路由
Outlet
它是嵌套路由的关键桥梁。可以把它想象成父组件中一个"子路由内容的插口"。React Router会根据当前URL,将匹配的子路由组件"插入"到这个<Outlet />的位置。
索引路由
它定义了当父路由被匹配但未指定具体子路由时,应该显示哪个子组件。这类似于为文件夹设置一个"默认文件"。如果没有索引路由,访问父路由(如/admin)时,父组件中的<Outlet />区域会是空的。
4.4.路由保护与权限控制:守护的页面
就像一个门卫,根据访客的身份(权限)决定是否放行。
受保护路由 (ProtectedRoute)
创建一个高阶组件或包裹组件来检查权限,决定是渲染目标组件还是重定向到登录页。
javascript
import { Navigate, Outlet, useLocation } from 'react-router-dom';
// 模拟的认证 Hook
function useAuth() {
return { user: { name: 'Alice', role: 'admin' } }; // 模拟已登录用户
}
// 受保护路由组件
function ProtectedRoute({ allowedRoles }) {
const { user } = useAuth();
const location = useLocation();
// 1. 检查是否登录
if (!user) {
// 未登录,重定向到登录页,并携带当前路径以便登录后跳回
return <Navigate to="/login" state={{ from: location }} replace />;
}
// 2. 检查角色权限(可选)
if (allowedRoles && !allowedRoles.includes(user.role)) {
// 已登录但角色不符,重定向到无权限页面
return <Navigate to="/unauthorized" replace />;
}
// 3. 权限验证通过,渲染子路由
return <Outlet />;
}
// 使用受保护路由
<Routes>
<Route path="/admin" element={<ProtectedRoute allowedRoles={['admin', 'editor']} />}>
<Route index element={<AdminDashboard />} />
<Route path="users" element={<UserManagement />} />
</Route>
<Route path="/login" element={<LoginPage />} />
<Route path="/unauthorized" element={<UnauthorizedPage />} />
</Routes>
核心逻辑:
ProtectedRoute是一个中间人,它首先检查用户是否登录。
如果未登录,使用<Navigate>组件将用户重定向到登录页,并通过state传递当前URL,以便登录后能跳回原页面。
如果已登录但角色不符,重定向到无权限页面。
如果所有检查通过,渲染<Outlet />,即显示被保护的子路由组件。
动态路由(根据权限加载路由)
更高级的方案是根据用户权限动态生成或过滤路由配置,通常结合后端权限接口。
javascript
// 伪代码示例:根据用户角色动态生成路由
function createRoutes(userRole) {
const commonRoutes = [
{ path: '/', element: <Home /> },
{ path: '/login', element: <Login /> },
];
const adminRoutes = [
{ path: '/admin', element: <ProtectedRoute allowedRoles={['admin']}><AdminLayout /></ProtectedRoute>, children: [
{ path: 'users', element: <UserList /> },
{ path: 'settings', element: <Settings /> },
]}
];
if (userRole === 'admin') {
return [...commonRoutes, ...adminRoutes];
} else {
return commonRoutes;
}
}
// 在应用中使用
const routes = createRoutes(user.role);
// 然后使用 <Routes><Route {...route} /></Routes> 或 useRoutes(routes) 来渲染
这种方式更灵活,但实现复杂度也更高,通常需要结合状态管理(如Redux、Context)来管理权限和路由配置。
4.5.Navigate vs useNavigate Hook
两者功能类似,但使用场景和时机不同。
<Navigate to="..." />
这是声明式的重定向组件。
它本质上是一个立即执行的导航指令。
当它被渲染时,会立即改变当前的URL并导航到目标路径。
常用于条件渲染中,如在受保护路由中检查权限。
useNavigate Hook
是编程式的重定向方式。
可以在事件处理函数、异步操作(如API调用)的回调中,根据逻辑调用navigate('/path')来实现跳转。
更灵活,适用于需要根据条件或异步结果决定跳转的场景。
5.高级特性:数据加载与路由模式
5.1. 数据加载 (loader 与 action)
React Router v6.4+ 引入了强大的数据路由(Data Router),允许在组件渲染前预加载和处理数据。
javascript
import { createBrowserRouter, RouterProvider, useLoaderData } from 'react-router-dom';
// 1. 定义 loader 函数:在路由匹配后、组件渲染前执行
const userLoader = async ({ params }) => {
const response = await fetch(`/api/users/${params.userId}`);
if (!response.ok) throw new Error('Failed to fetch user');
return response.json(); // 返回的数据会自动传递给组件
};
const userAction = async ({ request, params }) => {
const formData = await request.formData(); // 获取表单数据
const response = await fetch(`/api/users/${params.userId}`, {
method: 'POST',
body: formData
});
return response.json(); // 返回的数据也会传递给组件
};
// 2. 创建路由器并配置 loader 和 action
const router = createBrowserRouter([
{
path: "/users/:userId",
element: <UserProfile />,
// 指定数据加载函数
loader: userLoader,
// 指定数据操作函数
action: userAction,
},
]);
// 3. 在组件中使用 useLoaderData 获取数据
function UserProfile() {
const user = useLoaderData(); // 获取 loader 返回的用户数据
return <div>{user.name}</div>;
}
// 4. 在 App 中使用 RouterProvider 替代 BrowserRouter
function App() {
return <RouterProvider router={router} />;
}
核心优势
确保数据在组件渲染前就绪,避免"闪烁"或"空白"。
所有数据操作(读/写)都通过路由配置,更容易理解和维护。
可以集中处理数据加载错误,显示统一的错误页面。
Action
主要用于处理表单提交、按钮点击等写操作。
它与loader形成了完整的"数据获取 -> 修改 -> 获取新数据"闭环。
5.2. 两种路由模式的选择
React Router提供了两种路由模式,适用于不同的环境。
|-------------------|------------------------------------------------|----------------------------|---------------------------------------|--------------------------------------|
| 模式 | 实现原理 | URL 格式 | 适用场景 | 备注 |
| BrowserRouter | 使用 HTML5 History API (pushState, replaceState) | http://example.com/about | 现代 Web 应用 ,服务器支持自定义路由 | 需要服务器配置(如Nginx)将所有路由请求重定向到index.html |
| HashRouter | 使用 URL 的 hash (#) 来模拟路由 | http://example.com/#/about | 静态页面托管 (如GitHub Pages)、兼容旧浏览器 | URL中会带有#,对SEO不友好,但部署简单 |
绝大多数情况:使用BrowserRouter,体验更好。
特殊部署环境(如无法配置服务器):使用HashRouter作为临时方案。
5.3. RouterProvider 与 数据路由
RouterProvider 是 v6.4+ 引入的新组件,用于使用数据路由。
它替代了传统的BrowserRouter。
需要先用createBrowserRouter创建一个路由器实例(包含路由配置、loader、action等),然后将这个实例传给RouterProvider。
数据路由 是React Router v6的一个重大进化。
它将路由和数据加载紧密结合起来,让可以在路由配置阶段就定义好数据如何获取、如何处理,从而获得更流畅的用户体验和更整洁的代码结构。
它非常适合构建复杂的、数据驱动的应用。
6. React Router 核心流程全景图
为了让更直观地理解整个导航过程,我用一个流程图来展示用户点击链接后,React Router内部是如何协同工作的:

这个流程图展示了从用户点击链接到页面最终渲染的完整生命周期,其中loader是v6.4+版本的核心优化。
7.实战练习
功能模块
1. 团队成员管理

表单受控组件、状态提升、条件渲染。
以网格布局展示团队成员信息,支持根据状态(在职/离职)区分样式。
点击按钮弹出模态框,填写表单信息。
实现了父子组件通信(Props),子组件 `AddForm` 负责收集数据,父组件 `Form` 负责更新全局状态。
简单的非空校验与错误提示 UI。
生成默认邮箱地址。
2. 键盘事件可视化

`onKeyDown`、`onKeyUp` 事件、事件对象属性 (`e.key`, `e.code`, `e.ctrlKey`)。
在屏幕上渲染一个虚拟键盘,物理按键按下时,屏幕对应按键实时高亮。
单独高亮显示 Control、Shift、Alt 等功能键的锁定状态。
大字号展示当前按下的键位。
滚动记录用户的按键操作历史。
3. 鼠标事件追踪器

`onMouseMove`、`onClick`、`onDoubleClick`、坐标计算。
实时计算并展示四种不同的坐标系统:
-
Screen: 相对于物理屏幕。
-
Client: 相对于浏览器可视区域。
-
Page: 相对于整个文档(包含滚动条)。
-
Offset: 相对于目标元素的内部偏移。
在交互区域内实现一个跟随鼠标移动的圆形光标。
记录单双击次数及最后操作类型,通过 UI 面板实时反馈,避免使用原生 `alert`。
技术栈
- React 18+
- React Router v6 (`createBrowserRouter`)

- JavaScript (ES6+)
- CSS3 (Flexbox, Grid, CSS Variables, Transitions)
- `classnames` (用于动态类名管理)