第97课:React Router v6(上)——路由配置、BrowserRouter、Routes、Route
单页应用(SPA)的核心用户体验之一是无刷新的页面切换——用户点击链接,URL 更新,页面内容变化,但浏览器不会重新加载整个文档。React Router 是实现这一体验的事实标准库。React Router v6 相比 v5 进行了彻底的 API 重构:移除了 <Switch>、移除了 <Route> 的 component/render/children prop、引入了 <Routes> 组件和基于元素的 <Route> 配置、以及 useNavigate 和嵌套路由的简化写法。本节课将聚焦于 React Router v6 的核心基石:创建路由配置、使用 BrowserRouter 包裹应用、通过 <Routes> 和 <Route> 定义路径与组件的映射,以及使用 <Link> 和 <NavLink> 构建导航。
1. React Router v6 的核心架构
React Router v6 的核心理念是路由即组件。整个路由系统由三层组件构成:
- 路由器(Router):
BrowserRouter或HashRouter,负责将 URL 与 UI 同步,为整个应用提供路由上下文。 - 路由匹配器(Routes + Route):
<Routes>包裹一组<Route>,当 URL 变化时,遍历子<Route>,渲染第一个匹配的路由元素。 - 导航组件(Link、NavLink、useNavigate):提供声明式或命令式的导航方式,更新 URL 而无需刷新页面。
1.1 安装
1 | npm install react-router-dom |
React Router v6 的包名为 react-router-dom(DOM 绑定版本),内部依赖 react-router(核心库)。
1.2 最小可运行示例
1 | import { BrowserRouter, Routes, Route, Link } from 'react-router-dom'; |
在这个最小示例中:
BrowserRouter是整个路由系统的根组件,必须包裹在最外层。<Routes>是 v6 中替代 v5<Switch>的组件,负责匹配当前 URL 并渲染对应的<Route>。- 每个
<Route>通过path指定匹配的 URL 模式,通过elementprop 指定匹配后渲染的 React 元素。 <Link>渲染为<a>标签,但点击时拦截默认的页面刷新行为,仅更新 URL 并触发路由匹配。
2. BrowserRouter 与 HashRouter
React Router 提供两种路由器,对应两种不同的 URL 模式。
| 路由器 | URL 格式 | 服务器支持需求 | 适用场景 |
|---|---|---|---|
BrowserRouter |
https://example.com/about |
需要服务器配置(所有路径返回 index.html)。 |
生产环境,SEO 友好,推荐使用。 |
HashRouter |
https://example.com/#/about |
无需服务器配置。 | 静态文件托管(如 GitHub Pages)、无法配置服务器回退的环境。 |
BrowserRouter 使用 HTML5 History API(pushState、replaceState 和 popstate 事件)来管理 URL。当用户访问 /about 路径并刷新页面时,浏览器会向服务器请求 /about 路径。如果服务器未配置回退规则(将所有路径重定向到 index.html),将返回 404 错误。在生产环境中,这通常由 Nginx、Apache 或托管平台(Vercel、Netlify)自动处理。
HashRouter 使用 URL 的哈希部分(#)来存储路径。哈希部分的变化不会发送到服务器,因此无需任何服务器配置即可在任何静态主机上工作。缺点是 URL 中包含 #,不够美观,且对 SEO 不友好。
1 | // BrowserRouter(推荐) |
两者在使用方式上完全一致,仅导入不同。
2.1 basename:部署在子目录
如果你的应用部署在服务器的子目录下(如 https://example.com/my-app/),需要为路由器设置 basename 属性:
1 | <BrowserRouter basename="/my-app"> |
这样所有路由路径和导航都会自动添加 /my-app 前缀,无需在代码中硬编码。
3. <Routes> 与 <Route>:路由匹配与渲染
3.1 <Routes>:v6 的路由容器
<Routes> 替代了 v5 的 <Switch>。它会遍历其所有 <Route> 子元素,寻找最佳匹配当前 URL 的路径,并仅渲染第一个匹配的 <Route>。如果没有任何路由匹配,<Routes> 渲染 null(不渲染任何内容)。这与 v5 中 <Switch> 的行为一致。
v5 到 v6 的核心变化:
<Route>不再作为<Switch>的直接子元素,而是作为<Routes>的子元素。<Route>的component和renderprop 被移除,统一使用elementprop,接收一个 React 元素(如<Home />)。- 路由匹配顺序由
<Routes>内部的<Route>排列顺序决定,而非 v5 中的最佳匹配算法。React Router v6 使用评分算法:路径段越精确、越具体的路由优先级越高,而不再依赖定义顺序。
3.2 <Route> 的 path 与 element
1 | <Routes> |
- **
path**:URL 匹配模式。*表示通配符(匹配任意路径),用于 404 页面。 - **
element**:匹配成功后渲染的 React 元素。这是 v6 中<Route>的核心渲染方式。
3.3 index 路由:默认子路由
当一个路由有子路由(嵌套路由)时,父路由本身需要一个默认的子路由来渲染。此时使用 index 属性替代 path:
1 | <Routes> |
index 路由在父路由路径精确匹配时渲染(如上例中 / 精确匹配时渲染 <Home />)。它相当于 v5 中的 exact 匹配。
3.4 动态路由参数(URL 参数)
在 path 中使用 :paramName 定义动态段,在组件中通过 useParams Hook 读取。
1 | // 路由定义 |
多个动态参数:
1 | <Route path="/posts/:postId/comments/:commentId" element={<Comment />} /> |
3.5 404 页面:通配符路由
使用 path="*" 匹配所有未被前面路由匹配的 URL。
1 | <Routes> |
* 匹配所有路径,因此必须放在 <Routes> 的最后。React Router v6 的匹配算法会自动给更精确的路径更高的优先级,但将通配符放在最后仍是清晰明确的最佳实践。
4. 导航:Link 与 NavLink
4.1 Link:声明式导航
<Link> 渲染为 <a> 标签,但阻止了默认的页面刷新行为。它通过 to prop 指定目标路径。
1 | import { Link } from 'react-router-dom'; |
to 可以是一个字符串或一个对象(包含 pathname、search、hash):
1 | <Link to="/users?page=2">用户(第 2 页)</Link> |
4.2 NavLink:带激活状态的导航
NavLink 与 Link 类似,但额外支持激活状态样式。当当前 URL 与该链接的 to 匹配时,NavLink 会添加一个 active 类名(或自定义样式/类名)。
1 | import { NavLink } from 'react-router-dom'; |
NavLink 的 className 和 style 可以接收一个函数,该函数接收 { isActive, isPending } 对象,返回对应的字符串或样式对象。isActive 为 true 时表示当前 URL 与该链接匹配。
end prop:默认情况下,当 NavLink 的 to 路径是当前路径的前缀时,isActive 也为 true。例如 to="/" 在所有 URL 下都是 isActive。设置 end 属性后,仅当精确匹配整个路径时 isActive 才为 true:
1 | <NavLink to="/" end>首页</NavLink> {/* 仅在 URL 为 / 时激活 */} |
5. 综合示例:一个包含导航和 404 页面的博客框架
1 | import { BrowserRouter, Routes, Route, NavLink, useParams } from 'react-router-dom'; |
课后练习
一、概念自测(选择题 / 填空题)
(单选) React Router v6 中,替代 v5
<Switch>的组件是?
A.<Route>
B.<Routes>
C.<Outlet>
D.<Router>(单选) 在
<Route path="/users/:id" element={<Profile />}>中,组件Profile如何获取:id的值?
A.props.match.params.id
B.useParams().id
C.useRouteMatch().id
D.window.location.pathname(填空) 要创建一个匹配所有未被前面路由捕获的 URL 的“404 页面”,应使用
<Route ______ element={<NotFound />} />。(多选) 关于
NavLink的描述,哪些是正确的?
A.NavLink可以接收函数形式的className和style。
B.NavLink的isActive默认为模糊匹配(前缀匹配)。
C. 使用exactprop 可以实现精确匹配(仅完全匹配路径时激活)。
D.NavLink渲染的是<a>标签。
二、AI 编程任务:编写面向 AI 的提示词
场景:你需要为一个电商应用配置路由。要求如下:
- 使用
BrowserRouter包裹应用,basename为/shop。 - 定义以下路由:
/(首页)、/products(商品列表)、/products/:productId(商品详情)、/cart(购物车)、/checkout(结算页)、以及*(404 页面)。 - 所有页面组件使用简单的函数组件(仅需显示标题文字)。
- 创建一个全局导航栏,使用
NavLink高亮当前激活的链接(首页、商品、购物车)。 - 将导航栏和路由内容区域整合在一个
App组件中。
任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的 React 路由代码。提示词中需明确指定 BrowserRouter、basename、Routes、Route 的 path 和 element、以及 NavLink 的用法。
三、Agent 模式下的提示词示例
你是一个资深前端开发 Agent。请为一个电商应用创建 React Router v6 路由配置。需要创建以下文件:
src/pages/Home.jsx、src/pages/ProductList.jsx、src/pages/ProductDetail.jsx、src/pages/Cart.jsx、src/pages/Checkout.jsx、src/pages/NotFound.jsx:每个文件导出一个简单的函数组件,返回包含页面标题的<h2>。ProductDetail使用useParams获取并显示productId。src/components/Navigation.jsx:导出Navigation组件。使用<nav>包裹三个<NavLink>(首页/、商品/products、购物车/cart)。NavLink使用函数形式的className,激活时应用'active'类名(颜色为#4f46e5,加粗)。src/App.jsx:使用BrowserRouter basename="/shop"包裹整个应用。内部先渲染<h1>My Shop</h1>和<Navigation />,然后在<main>中使用<Routes>定义所有路由(/、/products、/products/:productId、/cart、/checkout、*)。每个<Route>的element属性引用对应的页面组件。- 所有文件添加简要注释。完成后列出所有文件内容。
四、面试真题与参考答案
题目(美团前端面试题):
请解释 React Router v6 与 v5 的主要区别。为什么 v6 将
<Route>的component和renderprop 替换为统一的elementprop?<Routes>组件如何决定渲染哪个子路由?与 v5 的<Switch>有何不同?
参考答案:
v6 与 v5 的核心区别包括:<Switch> 被 <Routes> 取代,<Route> 的 component/render/children prop 统一为 element prop,路由匹配算法从“第一个匹配”改为“最佳匹配”(基于路径段的评分),嵌套路由通过 <Outlet> 和相对路径简化,useNavigate 取代 useHistory,useParams、useLocation 等 Hook 的 API 保持但内部实现优化。
统一为 element prop 的原因是:component prop 在每次渲染时都会创建新的 React 元素,可能导致不必要的卸载和重新挂载;render prop 则每次渲染都调用渲染函数,无法利用 React 的元素缓存。element prop 直接接收一个 React 元素(<Home />),允许你在渲染前就确定元素树,React 可以更好地进行协调和缓存。同时,element 使得嵌套路由的 children 写法更自然——直接将 <Route> 嵌套在父 <Route> 内部,子路由的元素通过 <Outlet> 渲染。
<Routes> 使用评分算法(ranking algorithm)而非简单的顺序匹配。它为每条路由路径计算匹配得分,路径段越精确(静态段 > 动态段 > 通配符)、层级越多的路由得分越高,最终选择得分最高的路由进行渲染。这与 v5 的 <Switch> 从上到下匹配第一个的简单策略不同——v6 中路由的定义顺序不再影响匹配结果(除非得分完全相同)。
课后练习答案
一、概念自测答案
B
- 解析:
<Routes>是 v6 中替代<Switch>的组件,用于包裹<Route>并进行路由匹配。
- 解析:
B
- 解析:v6 中通过
useParamsHook 获取 URL 参数。A 是 v5 的类组件方式;C 是 v5 的 Hook;D 是原生浏览器 API,不推荐。
- 解析:v6 中通过
path="*"- 解析:通配符路径
*匹配所有未被前面路由捕获的 URL,通常用于 404 页面。
- 解析:通配符路径
A、B、D
- 解析:A 正确,
NavLink的className和style可接收函数;B 正确,默认是前缀匹配;D 正确,渲染为<a>标签。C 错误,v6 中使用endprop 实现精确匹配,而非exact。
- 解析:A 正确,
二、AI 编程任务参考答案(提示词示例)
示例提示词:
“请为一个电商应用创建 React Router v6 路由配置。要求:
BrowserRouter包裹,basename="/shop"。- 六个路由:
/(Home)、/products(ProductList)、/products/:productId(ProductDetail,使用 useParams 获取 productId)、/cart(Cart)、/checkout(Checkout)、*(NotFound)。- 导航栏使用
NavLink,激活时高亮。- 所有页面组件为简单函数组件,显示标题。输出所有文件。”