本文基于 React Router v6 编写,已剔除过时的 v5 语法,请放心食用。
为什么我们需要 React Router?
在开始写代码之前,咱们先通俗地聊聊 React Router 到底是干嘛的,以及为什么现在的 React 项目离不开它。
打个比方:从“翻书”到“PPT”
想象一下你正在看内容展示:
-
没有路由的传统网页 (MPA) :
就像在看书。每当你点击链接(翻页),浏览器都要把当前这页纸撕掉,重新去服务器拿一张新纸,重新排版、重新渲染。你会看到屏幕白屏闪烁一下,体验断层,效率也低。 -
使用了 React Router 的单页应用 (SPA) :
就像在播放 PPT。你的浏览器窗口就是那个投影幕布(容器),它始终不动。当你点击导航时,React Router 这个“放映员”只是不仅刷新地把 PPT 的某一页内容(组件)替换到了屏幕中间。幕布没变,只有内容在变。 这种体验就像原生 App 一样丝滑,没有白屏,无需等待整个页面重载。
它的三大核心超能力
-
无感切换 (No Refresh) :
这是最直观的体验。用户在页面间跳转时,浏览器不会重新加载整个 HTML。这对于用户体验是质的飞跃。 -
URL 也就是地图 (Source of Truth) :
你可能会问:“既然是换组件,我自己写个 if-else 渲染不就行了?”
不行!因为 Router 帮你保持了 UI 和 URL 的同步。- 比如你在京东看到一个商品的详情页,复制 URL 发给朋友,朋友打开后看到的必须是同一个商品,而不是首页。React Router 确保了 URL 变了,UI 跟着变;UI 变了,URL 也跟着变。
-
历史记录管理 (Time Travel) :
它自动帮你处理了浏览器的“后退”和“前进”按钮。如果没有它,你在单页应用里点后退,可能直接就退出了整个网站,用户绝对会抓狂。
适用场景
React Router 几乎是 React 开发的标准配置,特别是以下场景:
- 后台管理系统 (Admin Dashboard) :需要侧边栏常驻,只有右侧内容区切换(嵌套路由的绝佳舞台)。
- 内容型平台 (如掘金、知乎) :需要在列表页、详情页、个人中心之间流畅跳转。
- SaaS 工具类应用:操作逻辑复杂,需要保持应用状态不丢失。
一句话总结:如果你在写一个 React 应用,并且希望它有多个“页面”,别犹豫,React Router 就是你的必选装备,现在让我们快速入门React Router。
1. 快速上手与基本配置
首先,确保你的项目已经安装了最新版的路由库:
Bash
npm install react-router-dom
# 或者
yarn add react-router-dom
核心组件配置
在 v6 中,我们依然使用 BrowserRouter 包裹应用,但最大的变化在于路由的定义方式:
- Switch 已死,Routes 当立:不再使用 Switch,改用
<Router> - component 属性被弃用:现在统一使用 element 属性,并且传递的是组件实例(即
<Home />),而不是组件类或函数名。
App.js 标准配置示例
Jsx
import React from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';
function App() {
return (
<BrowserRouter>
{/* Routes 替代了 Switch */}
<Routes>
{/* element 属性替代了 component,注意这里传的是 JSX */}
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* v6 移除了 exact,默认就是精确匹配 */}
{/* 404 页面配置,path="*" 匹配所有未定义的路径 */}
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
);
}
export default App;
2. 导航与跳转
页面跳转是路由的灵魂。v6 在声明式导航和编程式导航上都有优化。
声明式导航:Link 与 NavLink
普通的跳转使用 ,这和以前一样:
Jsx
import { Link } from 'react-router-dom';
<Link to="/about">去关于页</Link>
重点:NavLink 的高亮样式
在 v5 中我们使用 activeClassName,但在 v6 中这个属性被移除了。className 现在接收一个函数,通过 isActive 参数来判断状态:
Jsx
import { NavLink } from 'react-router-dom';
import './Nav.css'; // 假设你定义了 .active 类
function NavBar() {
return (
<nav>
<NavLink
to="/"
className={({ isActive }) => isActive ? "nav-item active" : "nav-item"}
>
首页
</NavLink>
<NavLink
to="/about"
className={({ isActive }) => isActive ? "nav-item active" : "nav-item"}
>
关于
</NavLink>
</nav>
);
}
编程式导航:useNavigate (再见 useHistory)
这是很多老手最不习惯的地方:useHistory 没了!现在使用 useNavigate Hook。
Jsx
import { useNavigate } from 'react-router-dom';
function LoginPage() {
const navigate = useNavigate();
const handleLogin = () => {
// 登录逻辑...
// 1. 普通跳转
navigate('/dashboard');
// 2. 替换路由 (不留历史记录,类似 history.replace)
// navigate('/dashboard', { replace: true });
// 3. 传入数字实现前进/后退
// navigate(-1); // 后退一步
};
return <button onClick={handleLogin}>登录</button>;
}
3. 进阶路由:动态与嵌套
动态路由与参数获取
配置带参数的路径非常简单:
Jsx
// 路由配置
<Route path="/article/:id" element={<ArticleDetail />} />
在组件中获取参数,依然使用 useParams Hook:
Jsx
import { useParams } from 'react-router-dom';
function ArticleDetail() {
// 获取 URL 中的 id 参数
const { id } = useParams();
return <h1>正在阅读文章 ID: {id}</h1>;
}
嵌套路由与 Outlet (核心特性)
在后台管理系统中,我们经常需要“侧边栏 + 顶部栏 + 内容区”的布局。v6 引入了 组件,让嵌套路由的实现变得异常丝滑。
核心概念: 就像一个占位符,告诉 React Router:“子路由的内容就渲染在这里!”
1. 配置嵌套路由结构:
Jsx
// App.js
<Routes>
<Route path="/" element={<Home />} />
{/* 父路由:/admin */}
<Route path="/admin" element={<AdminLayout />}>
{/* 子路由:/admin/users */}
<Route path="users" element={<UserList />} />
{/* 子路由:/admin/settings */}
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
2. 在父组件中放置 Outlet:
Jsx
// AdminLayout.js
import { Outlet, Link } from 'react-router-dom';
function AdminLayout() {
return (
<div className="layout">
<aside>
<Link to="users">用户管理</Link>
<Link to="settings">系统设置</Link>
</aside>
<main>
{/* 见证奇迹的时刻:子路由组件将渲染在这里 */}
<Outlet />
</main>
</div>
);
}
4. 路由守卫与权限控制
v5 中我们要么写 包装器,要么用 render 属性。v6 不支持 render 属性了。
最佳实践:使用**高阶组件(Wrapper Component)**配合 组件。
实现 RequireAuth 组件
我们需要创建一个组件,用来拦截未登录的访问:
Jsx
// components/RequireAuth.js
import { Navigate, useLocation } from 'react-router-dom';
function RequireAuth({ children }) {
const isLogin = localStorage.getItem('token'); // 模拟鉴权逻辑
const location = useLocation();
if (!isLogin) {
// 未登录,强制跳转到登录页
// replace: true 确保用户登录后点后退不会又回到受保护页面
// state: 保存当前位置,登录后可以跳回来
return <Navigate to="/login" state={{ from: location }} replace />;
}
// 已登录,渲染子组件
return children;
}
export default RequireAuth;
在路由中使用
用 RequireAuth 包裹需要保护的页面组件:
Jsx
// App.js
<Routes>
<Route path="/login" element={<Login />} />
<Route
path="/dashboard"
element={
<RequireAuth>
<Dashboard />
</RequireAuth>
}
/>
</Routes>
这样写逻辑解耦,清晰明了!
5. 常用 Hooks 补充:处理查询参数
在 v6 中,获取 URL 上的查询参数(如 ?q=react&sort=asc)推荐使用 useSearchParams。它的用法和 useState 非常像。
Jsx
import { useSearchParams } from 'react-router-dom';
function SearchPage() {
const [searchParams, setSearchParams] = useSearchParams();
// 获取参数
const query = searchParams.get('q');
return (
<div>
<p>搜索关键词: {query}</p>
<button onClick={() => setSearchParams({ q: 'vue' })}>
改为搜索 Vue
</button>
</div>
);
}
6. 总结:v5 迁移 v6 速查表
为了方便老玩家迁移,我整理了这张核心 API 对照表:
| 功能 | React Router v5 | React Router v6 | 备注 |
|---|---|---|---|
| 路由容器 | <Switch> | <Routes> | 必须使用 |
| 定义路由 | <Route component={Home} /> | <Route element={<Home />} /> | 传组件实例 (JSX) |
| 重定向 | <Redirect to="/home" /> | <Navigate to="/home" /> | 组件名变更 |
| 导航 Hook | useHistory | useNavigate | API 完全重设计 |
| 当前路由样式 | activeClassName | className={(isActive) => ...} | 变为函数式控制 |
| 嵌套路由 | 手动配置路径 | <Outlet /> 组件 | 更加声明式和简洁 |
| 精确匹配 | 需要 exact 属性 | 默认精确匹配 | 不再需要 exact |
结语
React Router v6 的设计理念是Composition(组合)和Simplification(简化) 。虽然刚开始从 v5 迁移过来会有点不习惯,但一旦上手,你会发现它的 API 设计更加符合 React Hooks 的心智模型,嵌套路由的处理也更加优雅,通过本章的学习,您不仅掌握了 React Router 的基本概念和安装方法,还学会了如何配置和导航路由,实现了动态路由和嵌套路由,以及如何使用路由守卫和权限控制确保应用的安全性和用户体验。这些技能将帮助您在实际项目中构建结构清晰、功能完善的路由系统,提高应用的可用性和安全性。希望这些内容对您在实际项目中的开发工作有所帮助。