React-Router 如何使用?一篇搞懂 – wiki基地


一篇搞懂 React Router:构建强大的单页面应用导航

在构建现代 Web 应用时,尤其是使用 React 这样的库来开发单页面应用 (SPA) 时,客户端路由是不可或缺的一环。它允许我们在不进行全页面刷新的情况下,根据 URL 的变化来动态加载和切换不同的组件视图,从而提供流畅、类似原生应用的体验。

而提到 React 应用的路由,React Router 无疑是最流行、功能最完善的解决方案。它以声明式的方式帮助我们管理应用程序的导航,让复杂的路由逻辑变得直观且易于维护。

这篇文章将带你深入了解 React Router,从基础概念到核心组件,再到高级用法,力求通过一篇长文,让你全面掌握如何在你的 React 项目中运用 React Router 构建强大的导航系统。

本文涵盖的主要内容:

  1. 为什么需要客户端路由?SPAs 与传统多页应用的对比
  2. 什么是 React Router?它的作用和核心理念
  3. React Router v6:安装与基本配置
  4. 核心组件:BrowserRouter, Routes, Route 的使用
  5. 导航:LinkNavLink 的魔法
  6. 处理动态 URL:参数路由 (useParams)
  7. 嵌套路由 (Outlet):构建复杂布局
  8. 程序化导航 (useNavigate):代码控制跳转
  9. 处理 404 (页面未找到)
  10. 重定向 (Navigate)
  11. 获取位置信息 (useLocation)
  12. 常用 Hooks 总结
  13. 常见问题与最佳实践
  14. 从 v5 迁移到 v6 的变化 (简述)

准备好了吗?让我们开始这趟 React Router 学习之旅!

1. 为什么需要客户端路由?SPAs 与传统多页应用的对比

在深入 React Router 之前,理解为什么 SPA 需要客户端路由非常重要。

传统多页应用 (MPA):

  • 每次用户点击一个链接或提交表单,浏览器都会向服务器发送请求,服务器处理后返回一个新的 HTML 页面。
  • 整个页面会被重新加载,浏览器会丢弃当前的 DOM 结构,重新构建新的 DOM。
  • 优点:简单直接,搜索引擎友好 (SSR),对于内容展示型网站比较适合。
  • 缺点:每次页面跳转都需要完整的页面加载,用户体验相对较慢,尤其是网络状况不佳时。

单页面应用 (SPA):

  • 应用在第一次加载时会加载所有必要的 HTML、CSS 和 JavaScript 资源(或按需加载)。
  • 后续用户交互(如点击链接)不会触发全页面刷新。而是通过 JavaScript 拦截这些事件,动态地更新页面内容。
  • URL 变化通常通过浏览器的 History API 来模拟,使得用户仍然可以使用浏览器的前进/后退按钮,并且每个“页面”仍然有一个独立的 URL。
  • 优点:用户体验流畅,切换页面速度快,减少服务器压力(主要通过 API 获取数据)。
  • 缺点:首次加载可能较慢,对 SEO 不太友好(需要 SSR 或预渲染),需要客户端路由来管理视图切换。

React 开发的应用通常是 SPA。当你在 React 应用中点击一个“关于”链接时,我们不希望浏览器去请求一个新的 about.html 页面。我们希望 React 能够捕获这个点击事件,改变 URL 到 /about,然后卸载当前显示的组件(比如首页组件),并加载并渲染关于页面组件。这个过程,就是客户端路由的任务。

React Router 就是实现这一任务的标准库。

2. 什么是 React Router?它的作用和核心理念

React Router 是一个为 React 应用设计的声明式路由库。它的主要作用是:

  • 将 URL 与组件进行映射: 当 URL 匹配某个预定义的路径时,React Router 负责渲染对应的 React 组件。
  • 管理导航历史: 它利用浏览器的 History API 来管理浏览器的历史堆栈,使得前进/后退按钮能够正常工作。
  • 提供导航组件: 提供 Link, NavLink 等组件,方便用户点击进行页面导航,而不是使用传统的 <a> 标签(使用 <a> 标签会导致全页面刷新,违背 SPA 的初衷)。
  • 支持程序化导航: 允许你在代码逻辑中进行页面跳转(例如,用户登录成功后跳转到仪表盘)。
  • 处理嵌套路由: 允许在一个父级组件内部定义和渲染子路由,构建复杂的页面布局。

React Router 的核心理念是声明式路由。这意味着你不是编写命令式的代码来“跳转到这个 URL,然后加载那个组件”,而是通过声明的方式告诉 React Router:“当 URL 是 /about 时,渲染 <AboutPage /> 组件”。React Router 会监听 URL 的变化,并根据你的声明自动完成组件的切换。

3. React Router v6:安装与基本配置

我们将专注于当前主流版本 React Router v6。

安装:

使用 npm 或 yarn 在你的 React 项目中安装 react-router-domreact-router-dom 是用于 Web 应用的版本,包含了核心路由功能以及 DOM 相关的组件(如 BrowserRouter)。

“`bash
npm install react-router-dom

或者

yarn add react-router-dom
“`

基本配置:

要使用 React Router,你需要用一个 Router 组件来包裹你的整个应用。最常用的是 BrowserRouter

  • BrowserRouter: 使用 HTML5 History API (pushState, replaceState, popState) 来保持 UI 和 URL 的同步。这是现代 Web 应用推荐的方式。
  • HashRouter: 使用 URL 的哈希部分 (#) 来保持 UI 和 URL 的同步 (example.com/#/about)。不推荐用于现代应用,但在一些特殊环境(如不支持 History API 的旧浏览器或特定服务器配置)下可能有用。

我们通常在应用的入口文件(如 src/index.jssrc/main.jsx)中设置 BrowserRouter

“`jsx
// src/index.js 或 src/main.jsx
import React from ‘react’;
import ReactDOM from ‘react-dom/client’;
import { BrowserRouter } from ‘react-router-dom’;
import App from ‘./App’; // 你的根组件

const container = document.getElementById(‘root’);
const root = ReactDOM.createRoot(container);

root.render(

{/ 用 BrowserRouter 包裹你的根组件 /}




);
“`

将你的根组件 <App /> 放在 <BrowserRouter> 内部,这样整个应用就能够感知到路由的变化,并且在 <App /> 或其子组件的任何地方都可以使用 React Router 提供的路由功能和 Hooks。

4. 核心组件:Routes, Route 的使用

配置好 BrowserRouter 后,接下来就是定义具体的路由规则了。这主要依赖于 RoutesRoute 这两个核心组件。

  • <Routes>:

    • 在 v6 中取代了 v5 的 <Switch>
    • 它的作用是遍历其所有的子 <Route> 元素,并渲染第一个匹配当前 URL 的 <Route>
    • 它提高了路由匹配的效率和预测性。
  • <Route>:

    • 定义一个具体的路由规则。
    • 最重要的两个 props 是 pathelement
    • path: 定义该路由匹配的 URL 路径。
    • element: 当 URL 匹配 path 时,需要渲染的 React 元素(通常是一个组件)。在 v6 中,你传递一个 JSX 元素,例如 <HomePage />,而不是像 v5 那样使用 componentrender props。

通常,你会将这些路由定义放在你的根组件 (App.js) 或一个专门的路由配置组件中。

“`jsx
// src/App.js
import React from ‘react’;
import { Routes, Route } from ‘react-router-dom’;

// 导入你需要路由到的组件
import HomePage from ‘./pages/HomePage’;
import AboutPage from ‘./pages/AboutPage’;
import ContactPage from ‘./pages/ContactPage’;
import NotFoundPage from ‘./pages/NotFoundPage’; // 稍后会讲解 404

function App() {
return (

{/ 可以在这里放置导航栏等固定元素 /}
{/ /}

  {/* 定义你的路由规则集合 */}
  <Routes>
    {/* Route for the home page */}
    <Route path="/" element={<HomePage />} />

    {/* Route for the about page */}
    <Route path="/about" element={<AboutPage />} />

    {/* Route for the contact page */}
    <Route path="/contact" element={<ContactPage />} />

    {/* Catch-all route for 404 - MUST be the last route */}
    {/* 后面会详细讲解 */}
    <Route path="*" element={<NotFoundPage />} />
  </Routes>

  {/* 可以在这里放置页脚等固定元素 */}
  {/* <Footer /> */}
</div>

);
}

export default App;
“`

在上面的例子中:

  • 当 URL 是 / 时,React Router 会渲染 <HomePage />
  • 当 URL 是 /about 时,会渲染 <AboutPage />
  • 当 URL 是 /contact 时,会渲染 <ContactPage />
  • Routes 会从上往下查找,一旦找到第一个匹配的 <Route>,就会渲染其对应的 element 并停止查找(除非使用了嵌套路由,稍后讨论)。

关于路径匹配 (Path Matching) 在 v6 中:

React Router v6 对路径匹配进行了改进,默认情况下具有更高的优先级和更智能的匹配行为。你不再需要像 v5 那样显式使用 exact prop 来确保只匹配精确路径。v6 中的路径匹配更加“智能”, / 只会匹配根路径,/about 只会匹配 /about。如果需要匹配包含子路径的情况,通常会在父路径后使用 /* 或利用嵌套路由的特性。

5. 导航:LinkNavLink 的魔法

在 React 应用中进行页面导航时,切记不要使用原生的 <a> 标签,除非你是要跳转到外部网站。使用 <a> 标签会触发浏览器进行全页面刷新,导致你的 SPA 应用状态丢失,性能下降。

React Router 提供了专门用于内部导航的组件:LinkNavLink

  • <Link>:
    • 最基本的导航组件。
    • 渲染为一个 <a> 标签,但它会阻止默认的链接行为(全页面刷新),而是利用 History API 来改变 URL 并触发 React Router 渲染相应的组件。
    • 使用 to prop 指定跳转的目标路径。

“`jsx
// 例如,在你的导航栏组件中
import React from ‘react’;
import { Link } from ‘react-router-dom’;

function NavigationBar() {
return (

);
}

export default NavigationBar;
“`

  • <NavLink>:
    • <Link> 的一个特殊版本。
    • 除了具备 <Link> 的所有功能外,它还能在你访问的当前路由与链接的目标路径匹配时,自动给渲染的 <a> 元素添加一个特定的类名或样式,以便突出显示当前活跃的导航项。这对于制作导航菜单非常有用。
    • 可以使用 className prop (接收一个函数或字符串) 或 style prop (接收一个函数或对象) 来定义活跃时的样式。

使用函数形式的 classNamestyle 是 v6 的推荐方式,函数会接收一个对象参数,其中包含 isActive (布尔值) 和 isPending (布尔值,用于数据加载时)。

“`jsx
// 例如,在你的导航栏组件中
import React from ‘react’;
import { NavLink } from ‘react-router-dom’;

function NavigationBar() {
return (

);
}

export default NavigationBar;
“`

然后你可以在 CSS 中定义 .active-link 的样式,例如:

css
.active-link {
font-weight: bold;
color: blue;
}

通过 LinkNavLink 进行导航是用户交互触发路由变化的主要方式。

6. 处理动态 URL:参数路由 (useParams)

很多时候,你的 URL 需要包含动态信息,例如产品 ID、用户 ID 等。/products/123, /users/john-doe 这样的 URL 就包含了参数。React Router 允许你在 Routepath 中定义这些参数,并在匹配的组件中通过 useParams Hook 来获取它们。

定义带参数的路由:

path 字符串中使用冒号 : 加上参数名来定义一个 URL 参数。

“`jsx
// src/App.js 或你的路由配置
import { Routes, Route } from ‘react-router-dom’;
import ProductDetailPage from ‘./pages/ProductDetailPage’;
import UserProfilePage from ‘./pages/UserProfilePage’;

function App() {
return (

{/ 定义一个带有 ‘productId’ 参数的路由 /}
} />

  {/* 定义一个带有 'username' 参数的路由 */}
  <Route path="/users/:username" element={<UserProfilePage />} />

  {/* ... 其他路由 */}
</Routes>

);
}
“`

在组件中获取参数:

在匹配到参数路由的组件中,使用 useParams Hook 来获取 URL 中的参数值。useParams 返回一个对象,其键是你在 path 中定义的参数名,值是 URL 中对应的值。

“`jsx
// src/pages/ProductDetailPage.js
import React, { useEffect } from ‘react’;
import { useParams } from ‘react-router-dom’;

function ProductDetailPage() {
// 使用 useParams() 获取 URL 中的参数
const { productId } = useParams();

// 你可以使用这个 productId 来获取产品数据
useEffect(() => {
console.log(‘当前产品ID:’, productId);
// 例如,在这里调用 API 获取产品详情
// fetchProduct(productId).then(data => setProduct(data));
}, [productId]); // 当 productId 变化时重新运行 effect

return (

产品详情

您正在查看产品 ID 为: {productId} 的详情。

{/ 在这里渲染产品的详细信息 /}

);
}

export default ProductDetailPage;
“`

如果 URL 是 /products/abc-123,那么 useParams() 返回的对象将是 { productId: 'abc-123' }

7. 嵌套路由 (Outlet):构建复杂布局

在构建大型应用时,很多页面会有共同的布局或子导航。例如,用户仪表盘可能有概览、设置、消息等子页面,这些子页面共享仪表盘的侧边栏和顶部导航。React Router 的嵌套路由功能非常适合处理这种情况。

嵌套路由允许你在一个父级路由匹配时,在其对应的组件内部定义和渲染更多的子路由。

定义嵌套路由:

<Route> 中嵌套定义子 <Route>。父级 path 定义了基础路径,子级 path 通常是相对于父级的相对路径(不以 / 开头)。一个没有 path 的子 <Route> 可以作为父级路由的“索引”或默认子路由。

“`jsx
// src/App.js 或你的路由配置
import { Routes, Route } from ‘react-router-dom’;
import DashboardLayout from ‘./pages/DashboardLayout’; // 父组件
import DashboardOverview from ‘./pages/DashboardOverview’; // 子组件
import DashboardSettings from ‘./pages/DashboardSettings’; // 子组件
import DashboardMessages from ‘./pages/DashboardMessages’; // 子组件

function App() {
return (

{/ 定义仪表盘父级路由 /}
{/ 注意:父级 path 可以是 ‘/dashboard/‘ 或 ‘/dashboard’ /}
{/
在 v6 中,将子 Route 嵌套在父 Route 内部是定义嵌套路由的标准方式 /}
}>
{/
嵌套子路由 */}

    {/* 索引路由:当 path 为 /dashboard 时,匹配并渲染此组件 */}
    <Route index element={<DashboardOverview />} />

    {/* 当 path 为 /dashboard/settings 时,匹配并渲染此组件 */}
    <Route path="settings" element={<DashboardSettings />} />

    {/* 当 path 为 /dashboard/messages 时,匹配并渲染此组件 */}
    <Route path="messages" element={<DashboardMessages />} />

    {/* 如果需要,可以为仪表盘下的无效路径定义 404 */}
    {/* <Route path="*" element={<DashboardNotFound />} /> */}
  </Route>

  {/* ... 其他顶级路由 */}
</Routes>

);
}
“`

在父组件中渲染子路由 (Outlet):

当父级路由 (/dashboard) 匹配时,<DashboardLayout /> 组件会被渲染。要在 <DashboardLayout /> 内部渲染当前匹配的子路由(例如 <DashboardOverview />, <DashboardSettings /> 等),你需要使用 Outlet 组件。

Outlet 组件会渲染当前匹配的子路由元素。

“`jsx
// src/pages/DashboardLayout.js
import React from ‘react’;
import { Outlet, Link } from ‘react-router-dom’; // 导入 Outlet 和 Link

function DashboardLayout() {
return (

仪表盘

  {/* 仪表盘子导航 */}
  <nav>
    <ul>
      {/* Link to the index route (implicitly /dashboard) */}
      <li><Link to="/dashboard">概览</Link></li>
      {/* Link to /dashboard/settings */}
      <li><Link to="/dashboard/settings">设置</Link></li>
      {/* Link to /dashboard/messages */}
      <li><Link to="/dashboard/messages">消息</Link></li>
    </ul>
  </nav>

  <hr />

  {/* Outlet: 子路由会在这里渲染 */}
  <div className="dashboard-content">
    <Outlet />
  </div>
</div>

);
}

export default DashboardLayout;
“`

现在,当你访问:

  • /dashboard 时,会渲染 <DashboardLayout />,并且 <Outlet /> 位置渲染 <DashboardOverview />
  • /dashboard/settings 时,会渲染 <DashboardLayout />,并且 <Outlet /> 位置渲染 <DashboardSettings />
  • /dashboard/messages 时,会渲染 <DashboardLayout />,并且 <Outlet /> 位置渲染 <DashboardMessages />

嵌套路由是组织复杂应用视图和布局的强大工具。

8. 程序化导航 (useNavigate):代码控制跳转

有时候,你需要在某些事件发生后(例如,表单提交成功、用户登录或点击一个非 LinkNavLink 的元素)通过代码来控制页面跳转。React Router 提供了 useNavigate Hook 来实现这一功能。

useNavigate Hook 返回一个函数,调用这个函数就可以进行导航。

“`jsx
import React from ‘react’;
import { useNavigate } from ‘react-router-dom’;

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

const handleSubmit = (event) => {
event.preventDefault();
// 处理登录逻辑…

const isLoginSuccessful = true; // 假设登录成功

if (isLoginSuccessful) {
  console.log('登录成功,即将跳转...');
  // 调用 navigate 函数进行跳转
  navigate('/dashboard');

  // 也可以带上状态信息,在目标页面通过 useLocation().state 获取
  // navigate('/dashboard', { state: { message: 'Welcome Back!' } });

  // 也可以替代当前历史记录条目(比如登录成功后,按返回键不会回到登录页)
  // navigate('/dashboard', { replace: true });

  // 后退一步
  // navigate(-1);

  // 前进一步
  // navigate(1);
} else {
  console.log('登录失败');
}

};

return (

{/ 表单输入 /}

);
}

export default LoginForm;
“`

navigate 函数的常见用法:

  • navigate('/path'): 跳转到指定路径。
  • navigate('/path', { replace: true }): 跳转到指定路径并替换当前历史记录条目,这样用户点击浏览器后退按钮时不会回到原页面。
  • navigate('/path', { state: { key: value } }): 跳转并传递一些状态数据,可以在目标页面通过 useLocation().state 获取。
  • navigate(-1): 后退一步,相当于点击浏览器后退按钮。
  • navigate(1): 前进一步,相当于点击浏览器前进按钮。

9. 处理 404 (页面未找到)

当用户访问一个你的应用中没有定义匹配规则的 URL 时,通常应该显示一个“页面未找到”的页面 (404 Page)。在 React Router 中实现这一点非常简单:在你的 <Routes> 定义的最后添加一个 path="*"<Route>

path="*" 是一种通配符匹配,它会匹配任何没有被前面的路由匹配到的 URL。因为 Routes 是从上往下查找并只渲染第一个匹配的路由,所以将这个通配符路由放在最后,可以确保它只在所有其他路由都不匹配时才生效。

“`jsx
// src/App.js 或你的路由配置
import { Routes, Route } from ‘react-router-dom’;
import HomePage from ‘./pages/HomePage’;
import AboutPage from ‘./pages/AboutPage’;
// … 其他页面
import NotFoundPage from ‘./pages/NotFoundPage’; // 导入 404 页面组件

function App() {
return (

} />
} />
{/ … 其他具体的路由 … /}

  {/* 404 页面:匹配所有未被上面路由匹配到的路径 */}
  {/* 必须放在 Routes 的最后 */}
  <Route path="*" element={<NotFoundPage />} />
</Routes>

);
}
“`

NotFoundPage 组件可以是一个简单的 React 组件,显示“页面未找到”的信息。

“`jsx
// src/pages/NotFoundPage.js
import React from ‘react’;
import { useLocation } from ‘react-router-dom’; // 可以获取当前 URL

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

return (

404 – 页面未找到

抱歉,我们无法找到路径 {location.pathname} 对应的页面。

{/ 可以提供一个返回首页的链接 /}
{/ 返回首页 /}

);
}

export default NotFoundPage;
“`

10. 重定向 (Navigate)

有时候你需要将用户从一个 URL 自动重定向到另一个 URL。例如,你可能有一个旧的 URL /old-path,现在希望用户访问时自动跳转到 /new-path;或者,如果用户已经登录,当他们访问 /login 页面时,自动跳转到 /dashboard

React Router 提供了 Navigate 组件来实现声明式重定向。当 Navigate 组件被渲染时,它会立即改变当前的 URL 并触发 React Router 渲染匹配新 URL 的组件。

Navigate 组件接收一个 to prop 来指定重定向的目标路径,以及一个可选的 replace prop 来指定是否替换当前历史记录条目 (类似于 useNavigate{ replace: true } 选项)。

作为 Route 的 element 进行重定向 (常用场景:旧 URL 重定向):

“`jsx
// src/App.js 或你的路由配置
import { Routes, Route, Navigate } from ‘react-router-dom’;
import NewPage from ‘./pages/NewPage’;

function App() {
return (

{/ … 其他路由 … /}

  {/* 当用户访问 /old-path 时,自动重定向到 /new-path */}
  <Route path="/old-path" element={<Navigate to="/new-path" replace />} />

  {/* 定义 /new-path 的路由 */}
  <Route path="/new-path" element={<NewPage />} />

  {/* ... 404 路由 ... */}
</Routes>

);
}
``
这里使用
replace是个好的实践,这样用户点击后退不会回到/old-path(因为已经被/new-path替换了),而是回到访问/old-path` 之前的页面。

在组件内部条件式重定向 (常用场景:登录/权限):

你可以根据组件的状态或用户权限,在组件内部渲染 Navigate 组件来实现条件式重定向。

“`jsx
import React from ‘react’;
import { Navigate } from ‘react-router-dom’;

function LoginPage({ isLoggedIn }) {
// 如果用户已登录,则重定向到仪表盘
if (isLoggedIn) {
// 渲染 Navigate 组件会触发重定向
return ;
}

// 如果用户未登录,则渲染登录表单
return (

登录

{/ 登录表单等内容 /}

);
}

export default LoginPage;
“`

请注意,Navigate 应该在需要重定向的条件下被渲染。它不是一个 Hook 或函数调用,而是一个组件。

11. 获取位置信息 (useLocation)

除了 useParams 获取 URL 参数外,有时候你需要获取当前的完整 URL 信息,包括路径名 (pathname)、查询字符串 (search)、哈希值 (hash) 以及通过 navigate 传递的状态 (state)。useLocation Hook 可以帮助你获取这些信息。

useLocation 返回一个 location 对象,包含以下常用属性:

  • pathname: URL 的路径部分 (例如 /products/123)。
  • search: URL 的查询字符串部分 (例如 ?sort=price&page=1)。
  • hash: URL 的哈希部分 (例如 #section-details)。
  • state: 通过 navigateLinkstate prop 传递过来的状态数据。

“`jsx
import React from ‘react’;
import { useLocation } from ‘react-router-dom’;

function CurrentPageInfo() {
const location = useLocation(); // 调用 Hook 获取 location 对象

// 解析查询字符串 (需要单独处理,可以使用 URLSearchParams)
const queryParams = new URLSearchParams(location.search);
const sortOrder = queryParams.get(‘sort’);
const pageNumber = queryParams.get(‘page’);

// 获取导航传递的状态
const passedState = location.state;

console.log(‘当前 Location 对象:’, location);
console.log(‘当前路径:’, location.pathname);
console.log(‘查询字符串:’, location.search);
console.log(‘哈希值:’, location.hash);
console.log(‘传递的状态:’, passedState);
console.log(‘查询参数 sort:’, sortOrder);
console.log(‘查询参数 page:’, pageNumber);

return (

当前页面信息

路径: {location.pathname}

查询参数: {location.search}

{passedState &&

接收到的状态: {JSON.stringify(passedState)}

}

);
}

export default CurrentPageInfo;
“`

useLocation 在很多场景下都有用,比如在组件加载时根据 URL 的查询参数进行数据过滤或排序,或者在登录成功后根据传递的状态显示欢迎消息。

12. 常用 Hooks 总结

React Router v6 提供了几个非常有用的 Hook,我们来快速回顾一下:

  • useNavigate(): 返回一个函数,用于程序化导航(跳转、前进、后退)。
  • useParams(): 返回一个对象,包含当前路由匹配到的 URL 参数。
  • useLocation(): 返回一个对象,包含当前 URL 的位置信息 (pathname, search, hash, state)。
  • useMatch(pattern): 返回一个对象,包含当前 URL 是否与给定的模式匹配的信息。如果匹配,会包含匹配的路径、参数等。如果需要检查某个特定路径是否当前活跃,NavLink 通常更方便,但 useMatch 可以在更复杂的场景下判断匹配性。

这些 Hook 只能在函数组件内部或自定义 Hook 内部使用,并且必须在被 <BrowserRouter> 或其他 Router 组件包裹的组件树中使用。

13. 常见问题与最佳实践

  • 忘记包裹 BrowserRouter: 所有使用 React Router 功能的组件都必须是 <BrowserRouter> (或其他 Router) 的后代。如果你在组件中使用 useNavigateLink 但组件树没有被 Router 包裹,你会看到错误。
  • <Routes> 放在 <BrowserRouter> 之外: Routes 必须是 Router 组件的直接或间接后代。
  • 使用 <a> 标签进行内部导航: 再次强调,使用 LinkNavLink
  • 404 路由的位置: 通配符路由 (path="*") 必须放在 <Routes> 的最后,否则它会匹配所有路径,导致后面的路由永远无法被匹配。
  • 相对路径 vs. 绝对路径:
    • Linknavigate 函数的 to 参数可以接受相对路径(不以 / 开头)或绝对路径(以 / 开头)。相对路径在构建嵌套路由时非常方便。
    • <Route>path 在 v6 中通常是绝对匹配,但在嵌套路由中,子 Routepath 是相对于父级 path 的。
  • 利用 replace prop 进行重定向: 在登录后跳转或处理旧 URL 重定向时,使用 replace 替换当前历史记录条目,可以优化用户体验,防止用户后退到不应该回到的页面。
  • 组织路由配置: 当路由变得复杂时,可以将路由定义放在一个单独的文件中,而不是全部写在 App.js 中,提高可读性和维护性。
  • 懒加载/代码分割: 对于大型应用,可以结合 React.lazySuspense 对路由组件进行代码分割,只在需要时加载组件的代码,优化应用加载速度。这涉及到更高级的话题,但 React Router 完全支持。

14. 从 v5 迁移到 v6 的变化 (简述)

如果你之前使用过 React Router v5,迁移到 v6 会注意到一些主要变化:

  • Switch 变为 Routes: <Switch> 组件被 <Routes> 取代。<Routes> 的行为更直观,它会寻找最佳匹配而不是仅仅第一个匹配,但对于简单的路径,它仍会优先匹配更具体的路径。
  • component, render, children 变为 element: <Route> 组件不再使用 component, render, children props 来指定渲染什么,而是统一使用 element prop,并且需要传递一个 JSX 元素(例如 element={<MyComponent />})。
  • useHistory 变为 useNavigate: useHistory Hook 被 useNavigate Hook 取代。useNavigate 返回一个函数,而不是 history 对象。
  • 不再需要 exact: v6 的路径匹配更加智能,默认行为更接近 v5 带有 exact 的情况。对于父子路由,通过嵌套 <Route> 来定义关系。
  • 嵌套路由的定义方式: v6 标准的嵌套路由定义方式是将子 <Route> 放在父 <Route> 内部。
  • NavLinkactiveClassNameactiveStyle: 这些 props 被移除,现在通过 classNamestyle props 接收函数来实现活跃状态的样式控制。
  • Redirect 变为 Navigate: 组件 <Redirect><Navigate> 取代。

这些变化使得 v6 的 API 更加一致和简化。

结语

恭喜你!你已经详细了解了 React Router 在现代 React 应用中的核心用法。我们从客户端路由的必要性讲起,逐步深入到 React Router 的安装配置、核心组件 (BrowserRouter, Routes, Route)、导航方式 (Link, NavLink, useNavigate)、动态路由 (useParams)、嵌套路由 (Outlet)、错误处理 (404) 和重定向 (Navigate),以及一些常用 Hook (useLocation) 和最佳实践。

React Router 是构建流畅、可维护的 React SPA 不可或缺的工具。掌握它,你就能轻松地管理应用的各个视图和它们之间的跳转逻辑。

路由是前端开发的基石之一。多动手实践,尝试在自己的项目中使用不同的功能,你将能更牢固地掌握这些知识。

如果你遇到了更复杂的路由场景(如路由守卫、数据加载等),React Router 也提供了相应的能力或模式来处理,但这些通常属于更高级的话题。对于绝大多数应用来说,本文涵盖的内容已经足够让你构建一个功能完善的导航系统。

现在,是时候在你的 React 项目中运用这些知识,构建令人惊叹的单页面应用了!


发表评论

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

滚动至顶部