路由
React 应用将一个组件树渲染到一个 HTML 文档里。真实的网站有首页、关于页面、详情页面,每个都有自己的 URL。路由就是让这两件事配合的机制:URL 决定了渲染哪些组件,在应用中点击会更新 URL。React Router 是大多数 React 应用都在用的库,这个课程用整个章节来讲它,期间会构建一个名叫 VanLife 的车辆租赁应用。本章涵盖核心概念;嵌套路由和路由参数在此基础上扩展。
客户端路由:MPA vs SPA
在传统的多页面应用(MPA)中,每次导航都是一次往返。浏览器向服务器请求 /about,服务器组装一个 HTML 页面并发送回来,浏览器丢弃当前页面加载新的。整页切换在每次点击时都会产生明显的闪烁。
单页面应用(SPA)只加载一次文档。这个名字有点误导:从用户的角度看,网站仍然可以有很多页面。单一的是文档本身。第一次加载后,React 应用本身决定了每个 URL 显示什么。导航到 /about 时,组件在运行的应用内部切换,无需请求新文档,也没有闪烁。如果某个视图需要新的数据,应用会在后台获取 JSON 并就地更新,这是获取数据章节讲的模式。
**客户端路由**是让这一切工作的机制:一个库监听 URL、拦截导航,并渲染匹配的组件,而不是让浏览器重新加载。保持文档活跃带来真正的好处。整页加载会清除所有 React 状态;留在一个文档内意味着你的状态在每次导航后都能存活。
版本说明
本课程讲授 React Router 6。版本 7 将 React Router 与 Remix 合并并整合了包,所以 react-router 是当前的导入路径。在 v7 中,react-router-dom 作为重新导出保留,是 v6 和本课程使用的包,这就是为什么本手册中的每个示例都从它导入;版本 8 删除了它,所以新安装时从 react-router 导入这些组件。
React Router 文档记录了三种模式:声明式(本章讲的 BrowserRouter 和 Route 组件);数据模式(将 createBrowserRouter 与 loaders 和 actions 配对);以及框架模式(一个 Vite 插件,添加了类型化路由模块、代码分割和服务器渲染)。本章中的组件和 hook 名称在这些版本间保持不变。
BrowserRouter、Routes 和 Route
三个组件定义了基本设置:BrowserRouter 包裹应用并启用路由,Routes 保存你的路由定义,每个 Route 将一个路径映射到一个元素。
import { BrowserRouter, Routes, Route } from 'react-router-dom'
export default function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</BrowserRouter>
)
}BrowserRouter 在幕后是一个上下文提供者。用它包裹应用,让下面的每个组件都能访问路由的工具,这就是为什么它要放在最上层。有些代码库在导入时重命名它(import { BrowserRouter as Router }),因为全名太长。
每个 Route 接受两个 props。path 是 URL 中域名后面的部分:首页是 /,关于页面是 /about。element 是 URL 匹配该路径时要渲染的 JSX,作为一个实际的元素写,如 <Home /> 而不是 Home。当 URL 是 /about 时,React Router 在 Routes 组件所在的位置渲染 <About />;Routes 外面的任何内容都在每个页面上渲染。
访问没有匹配路由的路径时,什么都不会渲染。下面的通配符路由修复了这个问题。
Link:无页面加载的导航
在 HTML 中,在页面间移动的方式是锚标签,但对 SPA 来说完全是错的:点击 <a href="/about"> 会触发整页加载,丢弃运行的应用和其中的每一部分状态。React Router 的答案是 Link 组件。
import { Link } from 'react-router-dom'
function Header() {
return (
<nav>
<Link to="/">Home</Link>
<Link to="/about">About</Link>
</nav>
)
}Link 接受 to prop 而不是 href,读起来很自然:链接到下面的路由。在浏览器中它仍然渲染为真实的锚标签,所以屏幕阅读器、右键菜单和你的 CSS 选择器都会看到普通的 <a>。区别在于点击处理器:React Router 拦截点击,更新 URL,并交换渲染的组件,全部无需重新加载文档。一个显示为十的计数器在你浏览页面间移动时保持为十。在 React Router 应用内,内部导航总是通过 Link 进行;普通锚标签用于外部 URL。
NavLink 和活跃链接样式
导航栏通常会高亮显示你当前所在的页面。NavLink 就是为此而生的:它的行为像 Link,除了它的 className 和 style props 可以接受函数而不是普通值。React Router 用一个包含 isActive 布尔值的对象调用该函数,当链接的路由与当前 URL 匹配时为真。这是渲染 props 模式应用到 children 之外的 prop。
import { NavLink } from 'react-router-dom'
function Header() {
return (
<nav>
<NavLink
to="/about"
className={({ isActive }) => isActive ? 'active-link' : ''}
>
About
</NavLink>
</nav>
)
}函数返回什么就成为类名,所以 active-link 类只在 /about 是当前路由时应用,你的 CSS 处理其余的。style prop 的工作方式相同,使用内联样式对象:
const activeStyles = { fontWeight: 'bold', textDecoration: 'underline' }
<NavLink
to="/about"
style={({ isActive }) => isActive ? activeStyles : null}
>
About
</NavLink>类名还是内联样式是团队的约定;两者都完全支持,所以跟随项目已有的做法。
通配符 404 路由
用户可能会访问不存在的路径:过时的链接、打字错误、别人的损坏链接。没有备用方案,React Router 在那里什么都不渲染。解决办法是一个通配符路由,有时叫 splat 路由,其路径是单个星号。
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} />
</Routes>path="*" 匹配任何其他路由不声称的内容,很像 CSS 中的通用选择器。典型的 NotFound 组件配对一条简短消息和一个回到首页的 Link。列表中的顺序无关紧要:React Router 6 对每个路由和 URL 评分并选择最佳匹配,所以通配符只在没有更具体的匹配时获胜。把它放在最后是约定和可读性而不是必要性。
用 BrowserRouter 包裹你的应用,在 Routes 内列出你的页面作为 Route 组件,每个 path 获得自己的 element 来渲染。
使用 Link 而不是锚标签,这样点击会保持应用运行,添加一个 path="*" 路由,这样误输 URL 的访问者会看到友好的页面而不是空白屏幕。
接下来:嵌套路由和布局,路由获得共享的通用部分和路由中的路由。

