一篇搞懂 React Router:构建强大的单页面应用导航
在构建现代 Web 应用时,尤其是使用 React 这样的库来开发单页面应用 (SPA) 时,客户端路由是不可或缺的一环。它允许我们在不进行全页面刷新的情况下,根据 URL 的变化来动态加载和切换不同的组件视图,从而提供流畅、类似原生应用的体验。
而提到 React 应用的路由,React Router 无疑是最流行、功能最完善的解决方案。它以声明式的方式帮助我们管理应用程序的导航,让复杂的路由逻辑变得直观且易于维护。
这篇文章将带你深入了解 React Router,从基础概念到核心组件,再到高级用法,力求通过一篇长文,让你全面掌握如何在你的 React 项目中运用 React Router 构建强大的导航系统。
本文涵盖的主要内容:
- 为什么需要客户端路由?SPAs 与传统多页应用的对比
- 什么是 React Router?它的作用和核心理念
- React Router v6:安装与基本配置
- 核心组件:
BrowserRouter
,Routes
,Route
的使用 - 导航:
Link
和NavLink
的魔法 - 处理动态 URL:参数路由 (
useParams
) - 嵌套路由 (
Outlet
):构建复杂布局 - 程序化导航 (
useNavigate
):代码控制跳转 - 处理 404 (页面未找到)
- 重定向 (
Navigate
) - 获取位置信息 (
useLocation
) - 常用 Hooks 总结
- 常见问题与最佳实践
- 从 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-dom
。react-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.js
或 src/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
后,接下来就是定义具体的路由规则了。这主要依赖于 Routes
和 Route
这两个核心组件。
-
<Routes>
:- 在 v6 中取代了 v5 的
<Switch>
。 - 它的作用是遍历其所有的子
<Route>
元素,并渲染第一个匹配当前 URL 的<Route>
。 - 它提高了路由匹配的效率和预测性。
- 在 v6 中取代了 v5 的
-
<Route>
:- 定义一个具体的路由规则。
- 最重要的两个 props 是
path
和element
。 path
: 定义该路由匹配的 URL 路径。element
: 当 URL 匹配path
时,需要渲染的 React 元素(通常是一个组件)。在 v6 中,你传递一个 JSX 元素,例如<HomePage />
,而不是像 v5 那样使用component
或render
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. 导航:Link
和 NavLink
的魔法
在 React 应用中进行页面导航时,切记不要使用原生的 <a>
标签,除非你是要跳转到外部网站。使用 <a>
标签会触发浏览器进行全页面刷新,导致你的 SPA 应用状态丢失,性能下降。
React Router 提供了专门用于内部导航的组件:Link
和 NavLink
。
<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 (接收一个函数或对象) 来定义活跃时的样式。
- 是
使用函数形式的 className
或 style
是 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;
}
通过 Link
或 NavLink
进行导航是用户交互触发路由变化的主要方式。
6. 处理动态 URL:参数路由 (useParams
)
很多时候,你的 URL 需要包含动态信息,例如产品 ID、用户 ID 等。/products/123
, /users/john-doe
这样的 URL 就包含了参数。React Router 允许你在 Route
的 path
中定义这些参数,并在匹配的组件中通过 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
):代码控制跳转
有时候,你需要在某些事件发生后(例如,表单提交成功、用户登录或点击一个非 Link
或 NavLink
的元素)通过代码来控制页面跳转。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
: 通过navigate
或Link
的state
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) 的后代。如果你在组件中使用useNavigate
或Link
但组件树没有被 Router 包裹,你会看到错误。 - 将
<Routes>
放在<BrowserRouter>
之外:Routes
必须是 Router 组件的直接或间接后代。 - 使用
<a>
标签进行内部导航: 再次强调,使用Link
或NavLink
。 - 404 路由的位置: 通配符路由 (
path="*"
) 必须放在<Routes>
的最后,否则它会匹配所有路径,导致后面的路由永远无法被匹配。 - 相对路径 vs. 绝对路径:
Link
和navigate
函数的to
参数可以接受相对路径(不以/
开头)或绝对路径(以/
开头)。相对路径在构建嵌套路由时非常方便。<Route>
的path
在 v6 中通常是绝对匹配,但在嵌套路由中,子Route
的path
是相对于父级path
的。
- 利用
replace
prop 进行重定向: 在登录后跳转或处理旧 URL 重定向时,使用replace
替换当前历史记录条目,可以优化用户体验,防止用户后退到不应该回到的页面。 - 组织路由配置: 当路由变得复杂时,可以将路由定义放在一个单独的文件中,而不是全部写在
App.js
中,提高可读性和维护性。 - 懒加载/代码分割: 对于大型应用,可以结合
React.lazy
和Suspense
对路由组件进行代码分割,只在需要时加载组件的代码,优化应用加载速度。这涉及到更高级的话题,但 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>
内部。 NavLink
的activeClassName
和activeStyle
: 这些 props 被移除,现在通过className
和style
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 项目中运用这些知识,构建令人惊叹的单页面应用了!