WinddSnow

React-Router-v6-Part1-Route-Configuration-BrowserRouter-Routes-Route

字数统计: 4.1k阅读时长: 18 min
2026/08/01

第97课:React Router v6(上)——路由配置、BrowserRouterRoutesRoute

单页应用(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)BrowserRouterHashRouter,负责将 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import { BrowserRouter, Routes, Route, Link } from 'react-router-dom';

function Home() {
return <h2>首页</h2>;
}
function About() {
return <h2>关于我们</h2>;
}

function App() {
return (
<BrowserRouter>
<nav>
<Link to="/">首页</Link>
<Link to="/about">关于</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
);
}

在这个最小示例中:

  • BrowserRouter 是整个路由系统的根组件,必须包裹在最外层。
  • <Routes> 是 v6 中替代 v5 <Switch> 的组件,负责匹配当前 URL 并渲染对应的 <Route>
  • 每个 <Route> 通过 path 指定匹配的 URL 模式,通过 element prop 指定匹配后渲染的 React 元素。
  • <Link> 渲染为 <a> 标签,但点击时拦截默认的页面刷新行为,仅更新 URL 并触发路由匹配。

2. BrowserRouterHashRouter

React Router 提供两种路由器,对应两种不同的 URL 模式。

路由器 URL 格式 服务器支持需求 适用场景
BrowserRouter https://example.com/about 需要服务器配置(所有路径返回 index.html)。 生产环境,SEO 友好,推荐使用。
HashRouter https://example.com/#/about 无需服务器配置。 静态文件托管(如 GitHub Pages)、无法配置服务器回退的环境。

BrowserRouter 使用 HTML5 History API(pushStatereplaceStatepopstate 事件)来管理 URL。当用户访问 /about 路径并刷新页面时,浏览器会向服务器请求 /about 路径。如果服务器未配置回退规则(将所有路径重定向到 index.html),将返回 404 错误。在生产环境中,这通常由 Nginx、Apache 或托管平台(Vercel、Netlify)自动处理。

HashRouter 使用 URL 的哈希部分(#)来存储路径。哈希部分的变化不会发送到服务器,因此无需任何服务器配置即可在任何静态主机上工作。缺点是 URL 中包含 #,不够美观,且对 SEO 不友好。

1
2
3
4
5
// BrowserRouter(推荐)
import { BrowserRouter } from 'react-router-dom';

// HashRouter(无需服务端配置)
import { HashRouter } from 'react-router-dom';

两者在使用方式上完全一致,仅导入不同。

2.1 basename:部署在子目录

如果你的应用部署在服务器的子目录下(如 https://example.com/my-app/),需要为路由器设置 basename 属性:

1
2
3
4
5
6
<BrowserRouter basename="/my-app">
<Routes>
<Route path="/about" element={<About />} />
{/* 实际匹配的 URL 是 /my-app/about */}
</Routes>
</BrowserRouter>

这样所有路由路径和导航都会自动添加 /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>componentrender prop 被移除,统一使用 element prop,接收一个 React 元素(如 <Home />)。
  • 路由匹配顺序由 <Routes> 内部的 <Route> 排列顺序决定,而非 v5 中的最佳匹配算法。React Router v6 使用评分算法:路径段越精确、越具体的路由优先级越高,而不再依赖定义顺序。

3.2 <Route>pathelement

1
2
3
4
5
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users" element={<UserList />} />
<Route path="/users/:id" element={<UserDetail />} />
</Routes>
  • **path**:URL 匹配模式。* 表示通配符(匹配任意路径),用于 404 页面。
  • **element**:匹配成功后渲染的 React 元素。这是 v6 中 <Route> 的核心渲染方式。

3.3 index 路由:默认子路由

当一个路由有子路由(嵌套路由)时,父路由本身需要一个默认的子路由来渲染。此时使用 index 属性替代 path

1
2
3
4
5
6
7
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} /> {/* 默认子路由:/ */}
<Route path="about" element={<About />} /> {/* /about */}
<Route path="users" element={<Users />} /> {/* /users */}
</Route>
</Routes>

index 路由在父路由路径精确匹配时渲染(如上例中 / 精确匹配时渲染 <Home />)。它相当于 v5 中的 exact 匹配。

3.4 动态路由参数(URL 参数)

path 中使用 :paramName 定义动态段,在组件中通过 useParams Hook 读取。

1
2
3
4
5
6
7
8
9
10
11
12
// 路由定义
<Route path="/users/:userId" element={<UserProfile />} />

// UserProfile 组件
import { useParams } from 'react-router-dom';

function UserProfile() {
const { userId } = useParams(); // userId 为 string
console.log(userId); // 如果 URL 是 /users/42,输出 "42"

return <h2>用户 ID:{userId}</h2>;
}

多个动态参数

1
2
<Route path="/posts/:postId/comments/:commentId" element={<Comment />} />
// useParams() 返回 { postId: "...", commentId: "..." }

3.5 404 页面:通配符路由

使用 path="*" 匹配所有未被前面路由匹配的 URL。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* 通配符路由必须放在最后 */}
<Route path="*" element={<NotFound />} />
</Routes>

function NotFound() {
return (
<div>
<h1>404 - 页面未找到</h1>
<Link to="/">返回首页</Link>
</div>
);
}

* 匹配所有路径,因此必须放在 <Routes>最后。React Router v6 的匹配算法会自动给更精确的路径更高的优先级,但将通配符放在最后仍是清晰明确的最佳实践。


4.1 Link:声明式导航

<Link> 渲染为 <a> 标签,但阻止了默认的页面刷新行为。它通过 to prop 指定目标路径。

1
2
3
4
5
6
7
8
9
10
11
import { Link } from 'react-router-dom';

function Navigation() {
return (
<nav>
<Link to="/">首页</Link>
<Link to="/users">用户</Link>
<Link to="/users/42">用户详情(ID=42)</Link>
</nav>
);
}

to 可以是一个字符串或一个对象(包含 pathnamesearchhash):

1
2
3
4
<Link to="/users?page=2">用户(第 2 页)</Link>
<Link to={{ pathname: '/users', search: '?page=2', hash: '#section-1' }}>
用户(第 2 页,锚点)
</Link>

4.2 NavLink:带激活状态的导航

NavLinkLink 类似,但额外支持激活状态样式。当当前 URL 与该链接的 to 匹配时,NavLink 会添加一个 active 类名(或自定义样式/类名)。

1
2
3
4
5
6
7
8
9
10
11
import { NavLink } from 'react-router-dom';

<NavLink
to="/"
className={({ isActive }) => (isActive ? 'nav-link active' : 'nav-link')}
style={({ isActive }) => ({
fontWeight: isActive ? 'bold' : 'normal',
})}
>
首页
</NavLink>

NavLinkclassNamestyle 可以接收一个函数,该函数接收 { isActive, isPending } 对象,返回对应的字符串或样式对象。isActivetrue 时表示当前 URL 与该链接匹配。

end prop:默认情况下,当 NavLinkto 路径是当前路径的前缀时,isActive 也为 true。例如 to="/" 在所有 URL 下都是 isActive。设置 end 属性后,仅当精确匹配整个路径时 isActive 才为 true

1
<NavLink to="/" end>首页</NavLink> {/* 仅在 URL 为 / 时激活 */}

5. 综合示例:一个包含导航和 404 页面的博客框架

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
import { BrowserRouter, Routes, Route, NavLink, useParams } from 'react-router-dom';

// ---- 页面组件 ----
function Home() {
return <h2>🏠 博客首页</h2>;
}

function BlogList() {
return (
<div>
<h2>📝 文章列表</h2>
<ul>
<li><a href="/blog/1">React Router 入门</a></li>
<li><a href="/blog/2">React 状态管理</a></li>
</ul>
</div>
);
}

function BlogPost() {
const { postId } = useParams();
return <h2>📄 文章 ID:{postId}</h2>;
}

function NotFound() {
return (
<div style={{ textAlign: 'center', marginTop: 48 }}>
<h1>😕 404 - 页面未找到</h1>
<NavLink to="/">← 返回首页</NavLink>
</div>
);
}

// ---- 主导航 ----
function Navigation() {
const linkStyle = ({ isActive }) => ({
marginRight: 16,
textDecoration: 'none',
color: isActive ? '#4f46e5' : '#333',
fontWeight: isActive ? 'bold' : 'normal',
});

return (
<nav style={{ padding: '16px 0', borderBottom: '1px solid #eee' }}>
<NavLink to="/" end style={linkStyle}>首页</NavLink>
<NavLink to="/blog" style={linkStyle}>博客</NavLink>
</nav>
);
}

// ---- 根组件 ----
function App() {
return (
<BrowserRouter>
<div style={{ maxWidth: 800, margin: '0 auto', padding: 24 }}>
<h1>My Blog</h1>
<Navigation />
<main style={{ marginTop: 24 }}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/blog" element={<BlogList />} />
<Route path="/blog/:postId" element={<BlogPost />} />
<Route path="*" element={<NotFound />} />
</Routes>
</main>
</div>
</BrowserRouter>
);
}

export default App;

课后练习

一、概念自测(选择题 / 填空题)

  1. (单选) React Router v6 中,替代 v5 <Switch> 的组件是?
    A. <Route>
    B. <Routes>
    C. <Outlet>
    D. <Router>

  2. (单选)<Route path="/users/:id" element={<Profile />}> 中,组件 Profile 如何获取 :id 的值?
    A. props.match.params.id
    B. useParams().id
    C. useRouteMatch().id
    D. window.location.pathname

  3. (填空) 要创建一个匹配所有未被前面路由捕获的 URL 的“404 页面”,应使用 <Route ______ element={<NotFound />} />

  4. (多选) 关于 NavLink 的描述,哪些是正确的?
    A. NavLink 可以接收函数形式的 classNamestyle
    B. NavLinkisActive 默认为模糊匹配(前缀匹配)。
    C. 使用 exact prop 可以实现精确匹配(仅完全匹配路径时激活)。
    D. NavLink 渲染的是 <a> 标签。

二、AI 编程任务:编写面向 AI 的提示词

场景:你需要为一个电商应用配置路由。要求如下:

  • 使用 BrowserRouter 包裹应用,basename/shop
  • 定义以下路由:/(首页)、/products(商品列表)、/products/:productId(商品详情)、/cart(购物车)、/checkout(结算页)、以及 *(404 页面)。
  • 所有页面组件使用简单的函数组件(仅需显示标题文字)。
  • 创建一个全局导航栏,使用 NavLink 高亮当前激活的链接(首页、商品、购物车)。
  • 将导航栏和路由内容区域整合在一个 App 组件中。

任务要求:请写出一段完整的中文提示词,发送给 AI,使其生成符合上述要求的 React 路由代码。提示词中需明确指定 BrowserRouterbasenameRoutesRoutepathelement、以及 NavLink 的用法。

三、Agent 模式下的提示词示例

你是一个资深前端开发 Agent。请为一个电商应用创建 React Router v6 路由配置。需要创建以下文件:

  1. src/pages/Home.jsxsrc/pages/ProductList.jsxsrc/pages/ProductDetail.jsxsrc/pages/Cart.jsxsrc/pages/Checkout.jsxsrc/pages/NotFound.jsx:每个文件导出一个简单的函数组件,返回包含页面标题的 <h2>ProductDetail 使用 useParams 获取并显示 productId
  2. src/components/Navigation.jsx:导出 Navigation 组件。使用 <nav> 包裹三个 <NavLink>(首页 /、商品 /products、购物车 /cart)。NavLink 使用函数形式的 className,激活时应用 'active' 类名(颜色为 #4f46e5,加粗)。
  3. src/App.jsx:使用 BrowserRouter basename="/shop" 包裹整个应用。内部先渲染 <h1>My Shop</h1><Navigation />,然后在 <main> 中使用 <Routes> 定义所有路由(//products/products/:productId/cart/checkout*)。每个 <Route>element 属性引用对应的页面组件。
  4. 所有文件添加简要注释。完成后列出所有文件内容。

四、面试真题与参考答案

题目(美团前端面试题):

请解释 React Router v6 与 v5 的主要区别。为什么 v6 将 <Route>componentrender prop 替换为统一的 element prop?<Routes> 组件如何决定渲染哪个子路由?与 v5 的 <Switch> 有何不同?

参考答案

v6 与 v5 的核心区别包括:<Switch><Routes> 取代,<Route>component/render/children prop 统一为 element prop,路由匹配算法从“第一个匹配”改为“最佳匹配”(基于路径段的评分),嵌套路由通过 <Outlet> 和相对路径简化,useNavigate 取代 useHistoryuseParamsuseLocation 等 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 中路由的定义顺序不再影响匹配结果(除非得分完全相同)。


课后练习答案

一、概念自测答案

  1. B

    • 解析:<Routes> 是 v6 中替代 <Switch> 的组件,用于包裹 <Route> 并进行路由匹配。
  2. B

    • 解析:v6 中通过 useParams Hook 获取 URL 参数。A 是 v5 的类组件方式;C 是 v5 的 Hook;D 是原生浏览器 API,不推荐。
  3. path="*"

    • 解析:通配符路径 * 匹配所有未被前面路由捕获的 URL,通常用于 404 页面。
  4. A、B、D

    • 解析:A 正确,NavLinkclassNamestyle 可接收函数;B 正确,默认是前缀匹配;D 正确,渲染为 <a> 标签。C 错误,v6 中使用 end prop 实现精确匹配,而非 exact

二、AI 编程任务参考答案(提示词示例)

示例提示词
“请为一个电商应用创建 React Router v6 路由配置。要求:

  • BrowserRouter 包裹,basename="/shop"
  • 六个路由:/(Home)、/products(ProductList)、/products/:productId(ProductDetail,使用 useParams 获取 productId)、/cart(Cart)、/checkout(Checkout)、*(NotFound)。
  • 导航栏使用 NavLink,激活时高亮。
  • 所有页面组件为简单函数组件,显示标题。输出所有文件。”
CATALOG
  1. 1. 第97课:React Router v6(上)——路由配置、BrowserRouter、Routes、Route
    1. 1.1. 1. React Router v6 的核心架构
      1. 1.1.1. 1.1 安装
      2. 1.1.2. 1.2 最小可运行示例
    2. 1.2. 2. BrowserRouter 与 HashRouter
      1. 1.2.1. 2.1 basename:部署在子目录
    3. 1.3. 3. <Routes> 与 <Route>:路由匹配与渲染
      1. 1.3.1. 3.1 <Routes>:v6 的路由容器
      2. 1.3.2. 3.2 <Route> 的 path 与 element
      3. 1.3.3. 3.3 index 路由:默认子路由
      4. 1.3.4. 3.4 动态路由参数(URL 参数)
      5. 1.3.5. 3.5 404 页面:通配符路由
    4. 1.4. 4. 导航:Link 与 NavLink
      1. 1.4.1. 4.1 Link:声明式导航
      2. 1.4.2. 4.2 NavLink:带激活状态的导航
    5. 1.5. 5. 综合示例:一个包含导航和 404 页面的博客框架
    6. 1.6. 课后练习
      1. 1.6.1. 一、概念自测(选择题 / 填空题)
      2. 1.6.2. 二、AI 编程任务:编写面向 AI 的提示词
      3. 1.6.3. 三、Agent 模式下的提示词示例
      4. 1.6.4. 四、面试真题与参考答案
    7. 1.7. 课后练习答案
      1. 1.7.1. 一、概念自测答案
      2. 1.7.2. 二、AI 编程任务参考答案(提示词示例)