React Router 入门教程 – wiki基地


征服单页应用导航:React Router v6 深度入门教程

单页应用 (SPA) 带来了流畅的用户体验,页面切换无需重新加载,感觉就像桌面应用一样。然而,这也引入了一个新的挑战:如何在不进行传统页面刷新的情况下,实现页面之间的跳转、处理不同的 URL,并让用户能够使用浏览器的前进/后退按钮?

这就是客户端路由的职责,而 React Router 是 React 社区中最流行、功能最强大的客户端路由库之一。它让你能够将不同的 URL 地址映射到不同的 React 组件,从而在视觉上模拟多页应用的行为,但保留 SPA 的性能优势。

本教程将带你从零开始,深入理解 React Router v6 的核心概念和使用方法,并通过实际代码示例构建一个简单的多页面应用。

为什么需要客户端路由?

在传统的 Web 开发中,每次用户点击一个链接或提交一个表单,浏览器都会向服务器发送一个请求,服务器返回一个新的 HTML 页面,浏览器清除旧页面并渲染新页面。这被称为服务器端路由

在 SPA 中,初始加载时通常只会下载一个 HTML 文件(通常是 index.html),然后所有的页面内容都是通过 JavaScript 动态生成和更新的。在这种模式下:

  1. 用户体验: 切换页面时无需等待整个页面重新加载,速度更快,用户体验更佳。
  2. 性能: 减少了服务器请求和数据传输量(后续页面通常只加载必要的数据)。
  3. 开发体验: 可以在一个统一的框架下管理整个应用的状态和逻辑。

然而,纯粹的 SPA 也会带来一些问题:

  • 如何区分不同的“页面”? 我们的应用只有一个 HTML 文件,如何让 myapp.com/about 显示“关于”内容,而 myapp.com/contact 显示“联系我们”内容?
  • 如何使用浏览器的前进/后退按钮? 如果没有路由,每次内容更新都会覆盖历史记录,用户无法导航。
  • 如何分享特定页面的链接? 如果 URL 不变,用户无法收藏或分享应用内部某个具体内容的链接。

客户端路由库(如 React Router)通过监听 URL 的变化(通常使用浏览器的 History API 或 Hashchange 事件),然后在客户端根据 URL 匹配并渲染相应的 React 组件,巧妙地解决了这些问题。它更新 URL,但阻止浏览器的默认完全刷新行为。

React Router v6 的核心理念与优势

React Router v6 是 React Router 的最新主要版本,相较于 v5 引入了一些显著的变化和改进,使得路由的配置更加简洁和直观。其核心理念包括:

  • 组件化: React Router 的所有功能都是通过组件来实现的 (<BrowserRouter>, <Routes>, <Route>, <Link>),这与 React 本身的组件化思想完美契合。你可以像构建其他 UI 组件一样构建你的路由结构。
  • 声明式: 你不是命令式地告诉 React Router “当 URL 是 /about 时,加载 About 组件”,而是声明“如果 URL 匹配 /about,就渲染 <About /> 组件”。这种声明式风格与 React 的渲染方式一致。
  • Hooks 优先: v6 提供了强大的 Hooks (useParams, useNavigate, useLocation, useResolvedPath, useMatch),让你在函数组件中能够方便地访问路由信息和进行导航操作。
  • 嵌套路由更自然: v6 改进了嵌套路由的处理方式,通过 <Outlet /> 组件使得父子路由之间的协作更加清晰。
  • 更小的包体积: v6 在内部进行了优化,提供了更小的构建体积。

入门前的准备

在开始之前,请确保你已经安装了 Node.js、npm 或 yarn,并且对 React 的基础知识(组件、JSX、Props、State、Hooks)有一定的了解。

我们将使用 create-react-app 或 Vite 创建一个基本的 React 项目作为起点。如果你还没有项目,可以这样快速创建一个:

使用 create-react-app:

bash
npx create-react-app my-react-router-app
cd my-react-router-app

使用 Vite (推荐,更轻量快速):

bash
npm create vite@latest my-react-router-app --template react
cd my-react-router-app
npm install

创建项目后,你需要安装 React Router:

“`bash
npm install react-router-dom

或者

yarn add react-router-dom
“`

react-router-dom 是专门用于 Web 应用的 React Router 版本,它包含了 react-router 的核心功能以及用于 DOM 环境的特定组件(如 BrowserRouter, Link)。

核心组件详解与基本使用

React Router v6 最常用的核心组件包括:

  1. <BrowserRouter>: 路由的容器。它利用浏览器的 History API (pushState, replaceState, popstate 事件) 来保持 UI 和 URL 的同步。这是在大多数 Web 应用中最常用的路由器。你需要将你的整个应用(或至少是需要路由的部分)包裹在其中。
  2. <Routes>: v6 中引入的新组件,取代了 v5 的 <Switch>。它用于包裹一组 <Route> 组件。<Routes> 会遍历其所有的子 <Route>,并渲染第一个匹配当前 URL 的 <Route>
  3. <Route>: 定义单个路由。它有两个主要的 props:
    • path: 要匹配的 URL 路径。
    • element: 当 path 匹配成功时,要渲染的 React 元素 (JSX)。
  4. <Link>: 用于创建导航链接。它会渲染一个 <a> 标签,但会阻止默认的页面刷新行为,而是通过 History API 更新 URL,并通知 <BrowserRouter> 发生变化,从而触发路由匹配和组件渲染。其主要 props 是 to,指定要跳转的目标路径。

让我们来构建一个非常简单的应用,包含 Home 和 About 两个页面。

首先,创建两个简单的组件文件(例如在 src/pages/ 目录下):

src/pages/Home.js:

“`javascript
import React from ‘react’;

function Home() {
return (

首页

欢迎来到我们的网站!

);
}

export default Home;
“`

src/pages/About.js:

“`javascript
import React from ‘react’;

function About() {
return (

关于我们

这是关于我们的页面。

);
}

export default About;
“`

接下来,修改 src/App.js 来集成 React Router:

src/App.js:

“`javascript
import React from ‘react’;
import { BrowserRouter, Routes, Route, Link } from ‘react-router-dom’; // 导入核心组件

// 导入页面组件
import Home from ‘./pages/Home’;
import About from ‘./pages/About’;

function App() {
return (
// 1. 使用 BrowserRouter 包裹整个需要路由的应用部分

{/ 导航菜单 /}

    <hr /> {/* 分隔符 */}

    {/* 2. 使用 Routes 包裹所有 Route 定义 */}
    <Routes>
      {/* 3. 定义 Route:path="/" 匹配根路径,渲染 Home 组件 */}
      <Route path="/" element={<Home />} />

      {/* 定义 Route:path="/about" 匹配 /about 路径,渲染 About 组件 */}
      <Route path="/about" element={<About />} />
    </Routes>
  </div>
</BrowserRouter>

);
}

export default App;
“`

src/index.js (如果你使用的是 create-react-app) 或 src/main.jsx (如果你使用的是 Vite) 中,确保 App 组件被渲染即可,通常不需要额外修改,因为 <BrowserRouter> 已经包裹在 App.js 里了。

现在,运行你的应用 (npm startyarn dev)。你会看到页面上有一个导航菜单和下方的内容区域。点击“首页”链接,URL 会变为 /,下方显示 Home 组件的内容。点击“关于我们”链接,URL 变为 /about,下方显示 About 组件的内容。尝试使用浏览器的前进/后退按钮,你会发现它们按预期工作。

这就是 React Router 最基本的用法:包裹 -> 定义路由容器 -> 定义具体路由 -> 创建链接

深入理解 <Route> 的匹配

在 React Router v6 中,<Routes> 会找到第一个匹配当前 URL 的 <Route> 并渲染其 element

  • 精确匹配 (Exact Matching): 在 v5 中,根路径 / 默认会匹配所有以 / 开头的路径(如 /about, /contact),需要 exact prop 来实现精确匹配。但在 v6 中,匹配逻辑更加智能,<Route path="/"> 只会精确匹配根路径 /。如果你想要匹配以 / 开头的所有路径,可以使用通配符 /* (尽管在 <Routes> 内部通常不需要这样做,因为 <Routes> 只渲染第一个匹配项)。
  • 匹配优先级: <Routes> 从上往下查找匹配项。因此,更具体的路径应该放在前面,例如 /users/me 应该放在 /users/:id 前面,以确保 /users/me 被优先匹配。

构建更复杂的导航:嵌套路由

许多应用有嵌套的结构,比如一个 Dashboard 区域,里面有 Profile、Settings 等子页面,并且这些子页面可能共享一个侧边栏或顶栏布局。React Router v6 提供了强大的嵌套路由支持,通过 <Outlet /> 组件实现。

思路是:父路由负责渲染共享的布局部分和一个占位符 (<Outlet />),子路由则负责填充这个占位符中的内容。

我们来添加一个 Dashboard 区域,包含 Dashboard 主页和 Settings 子页面。

创建组件:

src/pages/Dashboard.js: (父组件,包含共享布局和 <Outlet>)

“`javascript
import React from ‘react’;
import { Link, Outlet } from ‘react-router-dom’; // 导入 Outlet

function Dashboard() {
return (

仪表盘

  <hr />

  {/* Outlet 会渲染匹配到的子路由的 element */}
  <div>
    <h3>Dashboard Content:</h3>
    <Outlet />
  </div>
</div>

);
}

export default Dashboard;
“`

src/pages/DashboardHome.js: (Dashboard 的主页内容)

“`javascript
import React from ‘react’;

function DashboardHome() {
return (

欢迎来到仪表盘主页!

{/ 这里可以放置仪表盘的概览内容 /}

);
}

export default DashboardHome;
“`

src/pages/DashboardSettings.js: (Settings 子页面内容)

“`javascript
import React from ‘react’;

function DashboardSettings() {
return (

这是仪表盘的设置页面。

{/ 这里是设置表单等内容 /}

);
}

export default DashboardSettings;
“`

修改 src/App.js 添加 Dashboard 路由和其嵌套路由:

src/App.js:

“`javascript
import React from ‘react’;
import { BrowserRouter, Routes, Route, Link } from ‘react-router-dom’;

// 导入页面组件
import Home from ‘./pages/Home’;
import About from ‘./pages/About’;
// 导入 Dashboard 相关的组件
import Dashboard from ‘./pages/Dashboard’;
import DashboardHome from ‘./pages/DashboardHome’; // 需要一个默认子路由的内容
import DashboardSettings from ‘./pages/DashboardSettings’;

function App() {
return (

    <hr />

    <Routes>
      {/* 基本路由 */}
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />

      {/* 定义 Dashboard 父路由 */}
      {/* path="/dashboard" 匹配 /dashboard 及其子路径 */}
      {/* element={<Dashboard />} 渲染 Dashboard 组件,它包含 <Outlet /> */}
      <Route path="/dashboard" element={<Dashboard />}>
        {/* 定义嵌套子路由 */}
        {/* index 路由:匹配父路由 path="/dashboard" 精确路径时渲染,作为默认内容 */}
        {/* path="settings" 匹配 /dashboard/settings */}
        <Route index element={<DashboardHome />} /> {/* 匹配 /dashboard */}
        <Route path="settings" element={<DashboardSettings />} /> {/* 匹配 /dashboard/settings */}
        {/* 可以在这里添加更多子路由,例如 <Route path="profile" element={<DashboardProfile />} /> */}
      </Route>

    </Routes>
  </div>
</BrowserRouter>

);
}

export default App;
“`

注意嵌套路由的定义方式:我们在父 <Route path="/dashboard" element={<Dashboard />}> 内部定义子 <Route>

  • 子路由的 path 可以是相对于父路由的 ("settings" 会匹配 /dashboard/settings) 或绝对路径 ("/dashboard/settings",但通常使用相对路径更灵活)。
  • index 路由 (<Route index element={<DashboardHome />} />) 是一个特殊的子路由,它没有 path,当父路由的 path 匹配时,它会被渲染到父路由的 <Outlet /> 中,通常用作父路由的默认内容(例如访问 /dashboard 时显示 Dashboard 主页)。

现在,访问 /dashboard 会渲染 Dashboard 组件,并在 <Outlet /> 位置渲染 DashboardHome。点击“设置”,URL 变为 /dashboard/settings<Outlet /> 位置的内容会切换为 DashboardSettings,而 Dashboard 组件的导航和布局部分会保持不变。

处理动态 URL 参数:useParams

很多时候,你的 URL 中会包含动态的部分,例如用户 ID (/users/123) 或产品 Slug (/products/react-book)。React Router 允许你在 path 定义中使用冒号 : 来标记这些动态参数。例如,/users/:userId/products/:productSlug.

要获取这些动态参数的值,在函数组件中可以使用 useParams hook。

创建一个 UserDetail 组件:

src/pages/UserDetail.js:

“`javascript
import React from ‘react’;
import { useParams } from ‘react-router-dom’; // 导入 useParams

function UserDetail() {
// 使用 useParams 获取 URL 中的动态参数
// 返回一个对象,键是你在 Route path 中定义的参数名 (例如 :userId),值是对应的 URL 部分
const { userId } = useParams();

// 通常你会根据 userId 去请求用户数据
const [user, setUser] = React.useState(null);
const [loading, setLoading] = React.useState(true);

React.useEffect(() => {
// 模拟数据加载
setTimeout(() => {
// 实际应用中这里会是 API 调用
const dummyUsers = {
‘1’: { id: 1, name: ‘Alice’ },
‘2’: { id: 2, name: ‘Bob’ },
};
setUser(dummyUsers[userId]);
setLoading(false);
}, 500);
}, [userId]); // 当 userId 变化时重新加载数据

if (loading) {
return

加载中…

;
}

if (!user) {
return

用户 ID 为 {userId} 的用户不存在

;
}

return (

用户详情

用户 ID: {userId}

用户名: {user.name}

{/ 显示更多用户详情 /}

);
}

export default UserDetail;
“`

src/App.js 中添加这个动态路由:

“`javascript
// … 其他导入

// 导入 UserDetail
import UserDetail from ‘./pages/UserDetail’;

function App() {
return (

    <hr />

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

      <Route path="/dashboard" element={<Dashboard />}>
        <Route index element={<DashboardHome />} />
        <Route path="settings" element={<DashboardSettings />} />
      </Route>

      {/* 定义动态路由 */}
      {/* :userId 是一个动态参数,它的值将可以在 useParams 中获取 */}
      <Route path="/users/:userId" element={<UserDetail />} />

    </Routes>
  </div>
</BrowserRouter>

);
}

export default App;
“`

现在,当你点击“用户详情 (ID 1)”时,URL 变为 /users/1UserDetail 组件会渲染,并通过 useParams() 获取到 { userId: '1' }。点击“用户详情 (ID 2)”时,URL 变为 /users/2useParams() 获取到 { userId: '2' },组件会根据新的 userId 更新显示内容。

程序化导航:useNavigate

并非所有的导航都来自用户的点击 <Link>。有时你需要在代码中进行页面跳转,例如:

  • 用户成功登录后跳转到仪表盘。
  • 表单提交成功后跳转到详情页。
  • 点击按钮触发特定操作后跳转。

这时可以使用 useNavigate hook。它返回一个 navigate 函数,调用这个函数就可以进行导航。

修改 Home 组件,添加一个跳转到 About 页面的按钮:

src/pages/Home.js:

“`javascript
import React from ‘react’;
import { useNavigate } from ‘react-router-dom’; // 导入 useNavigate

function Home() {
const navigate = useNavigate(); // 调用 hook 获取导航函数

const handleNavigateToAbout = () => {
// 调用 navigate 函数进行导航
// 参数是要跳转的路径
navigate(‘/about’);

// navigate 也支持传入一个数字,例如 navigate(-1) 返回上一页,navigate(1) 前进一页
// navigate('/', { replace: true }); // replace: true 相当于 history.replaceState,替换当前历史记录而不是新增

};

return (

首页

欢迎来到我们的网站!

);
}

export default Home;
“`

现在,当你访问首页并点击按钮时,应用会通过代码跳转到 /about 路径。

处理未匹配的路由:404 页面

如果用户访问了一个你的应用中没有定义的 URL,你通常希望显示一个“404 – Not Found”页面。在 React Router v6 中,你可以使用 path="*" 来匹配任何未被前面 <Route> 匹配的路径。这个“捕获所有”的路由应该放在 <Routes>最后

创建一个 NotFound 组件:

src/pages/NotFound.js:

“`javascript
import React from ‘react’;
import { useLocation } from ‘react-router-dom’; // 可以使用 useLocation 获取当前路径

function NotFound() {
const location = useLocation(); // 获取当前位置信息

return (

404 – 页面未找到

您尝试访问的路径 {location.pathname} 不存在。

{/ 可以添加一个链接回到首页 /}

回到首页

);
}

export default NotFound;
“`

src/App.js 中添加这个路由:

“`javascript
import React from ‘react’;
import { BrowserRouter, Routes, Route, Link } from ‘react-router-dom’;

// … 其他导入
import NotFound from ‘./pages/NotFound’; // 导入 NotFound

function App() {
return (

{/ … 导航菜单 /}


    <Routes>
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="/dashboard" element={<Dashboard />}>
        <Route index element={<DashboardHome />} />
        <Route path="settings" element={<DashboardSettings />} />
      </Route>
      <Route path="/users/:userId" element={<UserDetail />} />

      {/* 404 Route - 放在最后 */}
      {/* path="*" 会匹配任何未被上面 Route 匹配的路径 */}
      <Route path="*" element={<NotFound />} />

    </Routes>
  </div>
</BrowserRouter>

);
}

export default App;
“`

现在,访问 /abcde/non-existent-page 等任何未匹配的路径,都会显示 404 页面。

重定向:<Navigate> 组件

有时你需要将用户从一个路径自动重定向到另一个路径。例如,访问旧的 URL 需要跳转到新的 URL,或者在用户未登录时尝试访问需要认证的页面,将其重定向到登录页。

在 React Router v6 中,推荐使用 <Navigate> 组件进行声明式重定向。<Navigate> 组件渲染时就会触发导航,它取代了 v5 中的 <Redirect>

例如,假设你想将访问 /old-about 的用户重定向到 /about

src/App.js<Routes> 中添加:

“`javascript
import React from ‘react’;
import { BrowserRouter, Routes, Route, Link, Navigate } from ‘react-router-dom’; // 导入 Navigate

// … 其他导入

function App() {
return (

{/ … 导航 /}


    <Routes>
      {/* ... 其他 Route */}
      <Route path="/" element={<Home />} />
      <Route path="/about" element={<About />} />
      <Route path="/dashboard" element={<Dashboard />}>
        <Route index element={<DashboardHome />} />
        <Route path="settings" element={<DashboardSettings />} />
      </Route>
      <Route path="/users/:userId" element={<UserDetail />} />

      {/* 重定向示例 */}
      {/* 当路径匹配 "/old-about" 时,不渲染任何组件,而是直接导航到 "/about" */}
      <Route path="/old-about" element={<Navigate to="/about" replace />} /> {/* replace prop 类似 navigate({ replace: true }) */}

      {/* 404 Route */}
      <Route path="*" element={<NotFound />} />

    </Routes>
  </div>
</BrowserRouter>

);
}

export default App;
“`

现在,当你访问 /old-about 或点击“旧关于页面”链接时,URL 会自动变为 /about,并显示 About 页面内容。

你也可以在组件内部根据条件进行重定向:

“`javascript
// 在某个组件内部
import React from ‘react’;
import { Navigate } from ‘react-router-dom’;

function ProtectedComponent({ isAuthenticated }) {
if (!isAuthenticated) {
// 如果用户未认证,渲染 Navigate 组件进行重定向
return ;
}

// 否则,渲染受保护的内容
return (

这是只有登录用户才能看到的内容。

);
}

export default ProtectedComponent;
“`

然后在 <Routes> 中使用这个组件:

“`javascript
// … 在 App.js 或其他地方
// 假设你有某种方式获取用户认证状态
const [isAuthenticated, setIsAuthenticated] = React.useState(false); // 示例状态

// … 在 Routes 内部
} />
“`

更多有用的 Hooks

React Router v6 提供了一些其他实用的 Hooks:

  • useLocation: 返回当前的 location 对象,包含 pathname (当前 URL 路径), search (URL 中的查询字符串), hash (URL 中的哈希值) 等信息。这对于获取当前 URL 的详细信息或在 URL 变化时触发副作用很有用(例如数据跟踪)。
  • useSearchParams: 用于方便地读取和修改 URL 中的查询字符串参数 (query parameters)。它返回一个包含查询参数的对象和一个用于更新它们的函数。

示例 useLocation:

“`javascript
// 在任何函数组件中
import { useLocation } from ‘react-router-dom’;

function MyComponent() {
const location = useLocation();

console.log(location.pathname); // 例如: “/about”
console.log(location.search); // 例如: “?id=123&name=test”
console.log(location.hash); // 例如: “#section”

return (

当前路径: {location.pathname}

);
}
“`

示例 useSearchParams:

“`javascript
// 在任何函数组件中
import { useSearchParams } from ‘react-router-dom’;

function SearchResultsPage() {
// 返回 [searchParams, setSearchParams]
// searchParams 是一个 URLSearchParams 实例
const [searchParams, setSearchParams] = useSearchParams();

// 获取特定参数的值
const query = searchParams.get(‘query’);
const page = searchParams.get(‘page’) || ‘1’; // 提供默认值

// … 根据 query 和 page 加载数据

// 更新参数
const handlePageChange = (newPage) => {
// setSearchParams 可以接受一个对象或函数
setSearchParams({ query, page: newPage }); // URL 将更新为 ?query=…&page=…
};

return (

搜索结果

搜索关键词: {query}

当前页: {page}

);
}
“`

其他 Router 类型 (简要了解)

除了 <BrowserRouter>react-router-dom 还提供了其他类型的路由器,但在大多数 Web 应用中 <BrowserRouter> 是首选:

  • <HashRouter>: 使用 URL 的哈希部分 (#) 来同步 UI 和 URL(例如 myapp.com/#/about)。不依赖 History API,兼容性更好,但在 SEO 和外观上不如 BrowserRouter
  • <MemoryRouter>: 将 URL 历史保存在内存中,不会读取或写入浏览器的地址栏。主要用于测试或非浏览器环境 (如 React Native)。

在入门阶段,专注于掌握 <BrowserRouter> 即可。

总结与下一步

恭喜你!现在你已经掌握了 React Router v6 的核心概念和基本用法:

  • 理解了客户端路由的需求和 React Router 的作用。
  • 学会了如何安装和设置 React Router。
  • 掌握了 <BrowserRouter>, <Routes>, <Route>, <Link> 这四大核心组件的使用。
  • 理解了如何定义基本路由和创建导航链接。
  • 学会了如何利用 <Outlet> 和嵌套 <Route> 构建具有共享布局的复杂页面结构。
  • 知道了如何使用 useParams 获取动态 URL 参数。
  • 学会了使用 useNavigate 进行程序化导航。
  • 了解了如何通过 path="*" 实现 404 页面。
  • 掌握了使用 <Navigate> 进行重定向。
  • 简要了解了 useLocationuseSearchParams 等其他实用 Hooks。

这只是 React Router v6 功能的冰山一角。在实践中,你可能会遇到更复杂的场景,例如:

  • 代码分割和懒加载 (Lazy Loading): 如何只在访问某个路由时才加载对应的组件代码,以优化应用性能?React 的 React.lazy()Suspense 可以与 React Router 结合使用。
  • 数据加载 (Data Loading): 在渲染某个路由组件之前,如何预先加载它所需的数据?v6.4+ 引入了数据加载 API。
  • 认证和授权: 如何保护某些路由,只允许特定用户访问?通常通过自定义路由包装组件或使用 Loader/Action 实现。
  • 滚动恢复 (Scroll Restoration): 如何在页面切换时记住用户的滚动位置?

要深入学习这些高级主题,强烈建议查阅 React Router 官方文档。官方文档是最新、最权威的学习资源,包含详细的 API 参考、指南和示例。

现在,就开始在你自己的 React 项目中实践 React Router 吧!尝试将一个已有的多页应用重构成一个 SPA,或者从零开始构建一个具有复杂导航结构的单页应用。通过动手实践,你将更快地掌握 React Router 的强大功能。

祝你学习愉快!

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注

滚动至顶部