React框架学习文档(七)

七、React Router

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

React 路由 | 菜鸟教程

目录

[七、React Router](#七、React Router)

1.快速上手:安装与基本配置

[1.1. 安装 React Router](#1.1. 安装 React Router)

[1.2. 基本路由配置与使用](#1.2. 基本路由配置与使用)

[1.3.BrowserRouter 与 Routes](#1.3.BrowserRouter 与 Routes)

2.导航

2.1.声明式导航

2.2.编程式导航

[2.3.声明式 vs 编程式](#2.3.声明式 vs 编程式)

3.路由参数:在页面间传递信息

[3.1. 动态路由参数 (params)](#3.1. 动态路由参数 (params))

[3.2. 查询字符串参数 (searchParams)](#3.2. 查询字符串参数 (searchParams))

[3.3. 路由状态 (state)](#3.3. 路由状态 (state))

3.4.参数传递方式对比

4.嵌套路由:构建复杂的页面结构

[4.1. 基本嵌套路由](#4.1. 基本嵌套路由)

[4.2. 索引路由 (index 父路由)](#4.2. 索引路由 (index 父路由))

[4.3.Outlet 与 索引路由](#4.3.Outlet 与 索引路由)

4.4.路由保护与权限控制:守护的页面

[4.5.Navigate vs useNavigate Hook](#4.5.Navigate vs useNavigate Hook)

5.高级特性:数据加载与路由模式

[5.1. 数据加载 (loader 与 action)](#5.1. 数据加载 (loader 与 action))

[5.2. 两种路由模式的选择](#5.2. 两种路由模式的选择)

[5.3. RouterProvider 与 数据路由](#5.3. RouterProvider 与 数据路由)

[6. React Router 核心流程全景图](#6. React Router 核心流程全景图)

7.实战练习

功能模块

[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组件。

创建一个导航链接。它最终渲染为一个<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.实战练习

CappuccinoRose/React-AddressEvents-app: 这是一个基于 React 的前端实战项目,专注于演示和练习 React 中的核心事件处理机制。项目通过三个独立的功能模块,深入探讨了表单事件、鼠标事件和键盘事件的实际应用场景,并结合 React Router 实现了单页应用(SPA)的路由导航。

功能模块

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` (用于动态类名管理)
相关推荐
FFF-X2 小时前
前端字符串模糊匹配实现:精准匹配 + Levenshtein 编辑距离兜底
前端
消失的旧时光-19432 小时前
从拷贝到移动:C++ 移动构造与移动赋值是怎么被逼出来的?(附完整示例)
开发语言·c++
古译汉书2 小时前
部分.exe文件打开但是一直显示界面,点击任务栏持续无反应
开发语言·单片机·嵌入式硬件
2301_817497332 小时前
C++中的装饰器模式高级应用
开发语言·c++·算法
m0_549416662 小时前
C++编译期字符串处理
开发语言·c++·算法
m0_581124192 小时前
C++中的适配器模式实战
开发语言·c++·算法
Hi_kenyon2 小时前
Ref和Reactive都是什么时候使用?
前端·javascript·vue.js
Coding茶水间2 小时前
基于深度学习的狗品种检测系统演示与介绍(YOLOv12/v11/v8/v5模型+Pyqt5界面+训练代码+数据集)
开发语言·人工智能·深度学习·yolo·目标检测·机器学习
£漫步 云端彡2 小时前
Golang学习历程【第十篇 方法(method)与接收者】
开发语言·学习·golang