React Router:路由管理
在现代 Web 应用开发中,路由管理 是构建多页面或单页应用(SPA)的核心技术之一。React Router 是 React 生态中最受欢迎的路由管理库,它为开发者提供了强大的工具来实现页面导航、动态路由和权限控制等功能。React Router v6 是其最新版本,相较于之前的版本,它带来了更简洁的 API、更灵活的嵌套路由支持以及更强大的导航工具,显著提升了开发体验。
本文专为希望实现多页面应用的开发者设计,内容涵盖 React Router v6 的安装与配置、核心路由组件、动态路由、导航方式、嵌套路由和路由守卫等核心主题。我们将通过一个简单的电商网站案例(包含首页、商品列表和详情页)以及一个练习任务(实现受保护的用户中心),帮助你深入掌握 React Router v6 的核心技能。
文章目标
- 理解 React Router v6 的安装和基本配置。
- 掌握核心路由组件(如
<Routes>
、<Route>
)的使用。 - 实现动态路由和查询参数的处理。
- 学会使用导航组件(
<Link>
、<NavLink>
)和 Hook(useNavigate
)。 - 探索嵌套路由和路由守卫的实现方法。
- 通过电商网站案例和练习任务巩固所学知识。
1. React Router v6 简介
1.1 什么是 React Router?
React Router 是一个专为 React 应用设计的路由管理库,它允许开发者根据 URL 动态渲染不同的组件,从而实现页面导航和切换。在单页应用(SPA)中,React Router 通过客户端路由管理页面的显示,无需刷新浏览器即可实现流畅的页面跳转。它是 React 生态中不可或缺的一部分,尤其适用于需要多页面结构的复杂应用。
1.2 为什么选择 React Router v6?
React Router v6 是 React Router 的最新版本,相较于 v5 和更早的版本,它引入了许多重要改进和新特性:
- 更简洁的 API :移除了
<Switch>
组件,改为使用<Routes>
和<Route>
的组合,语法更直观。 - 更强大的嵌套路由 :通过
<Outlet>
组件实现灵活的布局管理。 - 动态路由增强:支持相对路径和更便捷的参数化路由。
- 内置导航 Hook :提供了
useNavigate
、useParams
等 Hook,简化路由操作。 - 性能优化:路由匹配和渲染更加高效。
这些新特性使得 React Router v6 成为 2025 年及未来构建现代 Web 应用的首选工具,尤其是在 React 19 的新特性(如 Server Components)逐渐普及的背景下。
2. 安装与配置 React Router v6
2.1 安装
在 React 项目中安装 React Router v6,只需运行以下命令:
bash
npm install react-router-dom@6
注意 :确保指定版本
@6
,以避免安装旧版本或其他不兼容的版本。
2.2 基本配置
React Router v6 使用 <BrowserRouter>
作为路由容器,通常在应用的根组件中进行配置。<BrowserRouter>
利用 HTML5 History API 管理路由,支持干净的 URL(如 /about
而不是 #/about
)。
代码示例:
js
// main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
ReactDOM.createRoot(document.getElementById('root')).render(
<BrowserRouter>
<App />
</BrowserRouter>
);
<BrowserRouter>
:最常用的路由容器,适合大多数 Web 应用。- 其他路由器选项 :
<HashRouter>
:使用 URL 的#
部分管理路由,适合静态文件部署。<MemoryRouter>
:将路由存储在内存中,适用于测试或服务器端渲染(SSR)场景。
在 App
组件中,我们将定义具体的路由规则。
3. 核心路由组件
React Router v6 的核心组件包括 <Routes>
和 <Route>
,它们共同定义了应用的路由结构。
3.1 <Routes>
和 <Route>
<Routes>
:路由容器的根组件,用于包裹所有<Route>
。<Route>
:定义单个路由规则,通过path
属性匹配 URL,通过element
属性指定渲染的组件。
代码示例:
js
// App.jsx
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import NotFound from './pages/NotFound';
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="*" element={<NotFound />} />
</Routes>
);
}
path="/"
: 匹配根路径,渲染Home
组件。path="/about"
: 匹配/about
路径,渲染About
组件。path="*"
: 通配符,匹配所有未定义的路径,渲染NotFound
组件(404 页面)。
v6 新特性 :相比 v5 的
<Switch>
,<Routes>
更加智能,能够自动匹配最佳路由,并支持嵌套路由的优化。
3.2 动态路由
动态路由允许根据 URL 中的参数渲染不同的内容。例如,/product/1
和 /product/2
可以渲染同一个组件,但显示不同的商品详情。
代码示例:
js
// App.jsx
import { Route } from 'react-router-dom';
import ProductDetail from './pages/ProductDetail';
function App() {
return (
<Routes>
<Route path="/product/:id" element={<ProductDetail />} />
</Routes>
);
}
:id
:动态参数,表示 URL 中的变量部分。- 示例 URL:
/product/1
会将id
设置为"1"
。
在 ProductDetail
组件中,使用 useParams
Hook 获取参数:
js
// pages/ProductDetail.jsx
import { useParams } from 'react-router-dom';
function ProductDetail() {
const { id } = useParams();
return <h1>商品 ID: {id}</h1>;
}
useParams
:返回一个对象,包含所有动态参数。
3.3 查询参数
查询参数通过 URL 的 ?key=value
形式传递,常用于筛选、搜索等场景。React Router v6 提供了 useSearchParams
Hook 来处理查询参数。
代码示例:
js
// pages/ProductList.jsx
import { useSearchParams } from 'react-router-dom';
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category');
return (
<div>
<h1>商品列表 - {category || '全部'}</h1>
<button onClick={() => setSearchParams({ category: 'electronics' })}>
筛选电子产品
</button>
</div>
);
}
useSearchParams
:返回一个数组,包含当前查询参数对象和更新函数。searchParams.get('category')
:获取category
参数的值。setSearchParams
:更新 URL 的查询参数,例如点击按钮后 URL 变为/products?category=electronics
。
4. 导航:Link、NavLink 与 useNavigate
导航是路由管理的核心功能之一,React Router v6 提供了多种方式来实现页面跳转。
4.1 <Link>
<Link>
组件用于创建导航链接,点击时跳转到指定路径,避免页面刷新。
代码示例:
js
import { Link } from 'react-router-dom';
function Navbar() {
return (
<nav>
<Link to="/">首页</Link> |{' '}
<Link to="/about">关于我们</Link>
</nav>
);
}
to
属性:指定跳转的目标路径。<Link>
渲染为<a>
标签,但通过 React Router 内部机制实现客户端导航。
4.2 <NavLink>
<NavLink>
是 <Link>
的增强版,可以根据当前路径动态调整样式,例如高亮当前导航项。
代码示例:
js
import { NavLink } from 'react-router-dom';
function Navbar() {
return (
<nav>
<NavLink
to="/"
className={({ isActive }) => (isActive ? 'active' : '')}
>
首页
</NavLink>{' '}
|{' '}
<NavLink
to="/about"
className={({ isActive }) => (isActive ? 'active' : '')}
>
关于我们
</NavLink>
</nav>
);
}
className
:接受一个函数,参数isActive
表示当前路径是否匹配。- 示例 CSS:
css
.active {
color: red;
font-weight: bold;
}
4.3 useNavigate
Hook
useNavigate
Hook 允许在组件内部以编程方式导航,适用于事件驱动的场景(如按钮点击、表单提交)。
代码示例:
js
import { useNavigate } from 'react-router-dom';
function Login() {
const navigate = useNavigate();
const handleLogin = () => {
// 模拟登录逻辑
navigate('/dashboard');
};
return <button onClick={handleLogin}>登录</button>;
}
navigate('/dashboard')
:跳转到/dashboard
。navigate(-1)
:返回上一页,类似浏览器的"后退"按钮。navigate('/path', { replace: true })
:替换当前历史记录,而不是添加新记录。
v6 新特性 :
useNavigate
替代了 v5 的useHistory
,API 更直观且功能更强大。
5. 嵌套路由与路由守卫
5.1 嵌套路由
嵌套路由允许在父路由中定义子路由,常用于实现包含公共布局的页面(如导航栏 + 主内容)。
代码示例:
js
// App.jsx
import { Routes, Route, Outlet } from 'react-router-dom';
import Layout from './components/Layout';
import Home from './pages/Home';
import Dashboard from './pages/Dashboard';
import Settings from './pages/Settings';
function App() {
return (
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="dashboard" element={<Dashboard />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
);
}
// components/Layout.jsx
import { Outlet } from 'react-router-dom';
function Layout() {
return (
<div>
<nav>导航栏</nav>
<main>
<Outlet /> {/* 子路由渲染位置 */}
</main>
</div>
);
}
<Route path="/" element={<Layout />}>
:父路由,渲染Layout
组件。<Outlet />
:子路由的占位符,指定子组件的渲染位置。<Route index>
:默认子路由,匹配父路径/
时渲染。
v6 新特性 :
<Outlet>
替代了 v5 中手动传递children
,嵌套路由更加简洁。
5.2 路由守卫
路由守卫用于保护某些路由,确保用户在访问受限页面前满足特定条件(如已登录)。React Router v6 没有内置的守卫组件,但可以通过自定义组件实现。
实现方式:
js
import { Navigate, useLocation } from 'react-router-dom';
function ProtectedRoute({ children }) {
const isAuthenticated = false; // 模拟认证状态
const location = useLocation();
if (!isAuthenticated) {
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// 使用
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
ProtectedRoute
:检查认证状态的自定义组件。<Navigate>
:未认证时重定向到/login
。state={``{ from: location }}
:记录来源路径,登录后可返回。
6. 实践案例:电商网站
我们将通过一个简单的电商网站案例,综合应用以上知识。网站包含以下页面:
- 首页:欢迎页面。
- 商品列表:展示商品,支持筛选。
- 商品详情:根据商品 ID 显示详情。
6.1 项目结构
src/
├── pages/
│ ├── Home.jsx
│ ├── ProductList.jsx
│ ├── ProductDetail.jsx
├── components/
│ ├── Navbar.jsx
├── App.jsx
├── main.jsx
6.2 路由配置
js
// App.jsx
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import ProductList from './pages/ProductList';
import ProductDetail from './pages/ProductDetail';
import Navbar from './components/Navbar';
function App() {
return (
<div>
<Navbar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products" element={<ProductList />} />
<Route path="/product/:id" element={<ProductDetail />} />
</Routes>
</div>
);
}
6.3 导航栏
js
// components/Navbar.jsx
import { NavLink } from 'react-router-dom';
function Navbar() {
return (
<nav>
<NavLink to="/" className={({ isActive }) => (isActive ? 'active' : '')}>
首页
</NavLink>{' '}
|{' '}
<NavLink
to="/products"
className={({ isActive }) => (isActive ? 'active' : '')}
>
商品列表
</NavLink>
</nav>
);
}
6.4 首页
js
// pages/Home.jsx
function Home() {
return <h1>欢迎来到电商网站</h1>;
}
6.5 商品列表
js
// pages/ProductList.jsx
import { useSearchParams } from 'react-router-dom';
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category');
const products = [
{ id: 1, name: '手机', category: 'electronics' },
{ id: 2, name: '书籍', category: 'books' },
];
const filteredProducts = category
? products.filter((p) => p.category === category)
: products;
return (
<div>
<h1>商品列表 - {category || '全部'}</h1>
<button onClick={() => setSearchParams({ category: 'electronics' })}>
电子产品
</button>
<button onClick={() => setSearchParams({ category: 'books' })}>
书籍
</button>
<button onClick={() => setSearchParams({})}>全部</button>
<ul>
{filteredProducts.map((product) => (
<li key={product.id}>
<a href={`/product/${product.id}`}>{product.name}</a>
</li>
))}
</ul>
</div>
);
}
6.6 商品详情
js
// pages/ProductDetail.jsx
import { useParams, useNavigate } from 'react-router-dom';
function ProductDetail() {
const { id } = useParams();
const navigate = useNavigate();
const products = {
1: { name: '手机', price: 2999 },
2: { name: '书籍', price: 59 },
};
const product = products[id] || { name: '未知商品', price: 0 };
return (
<div>
<h1>{product.name}</h1>
<p>价格: ¥{product.price}</p>
<button onClick={() => navigate('/products')}>返回列表</button>
</div>
);
}
这个案例展示了动态路由(/product/:id
)、查询参数(筛选商品)和导航(<NavLink>
和 useNavigate
)的实际应用。
7. 练习:添加受保护的用户中心
现在,请尝试为电商网站添加一个受保护的 用户中心,包含以下要求:
- 创建
/user
路由,渲染UserCenter
组件。 - 使用
ProtectedRoute
保护该路由,仅允许已登录用户访问。 - 在
UserCenter
中添加一个嵌套路由/user/profile
,渲染Profile
组件。
参考实现
7.1 更新路由配置
js
// App.jsx
import { Routes, Route } from 'react-router-dom';
import Home from './pages/Home';
import ProductList from './pages/ProductList';
import ProductDetail from './pages/ProductDetail';
import UserCenter from './pages/UserCenter';
import Profile from './pages/Profile';
import ProtectedRoute from './components/ProtectedRoute';
import Navbar from './components/Navbar';
function App() {
return (
<div>
<Navbar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products" element={<ProductList />} />
<Route path="/product/:id" element={<ProductDetail />} />
<Route
path="/user"
element={
<ProtectedRoute>
<UserCenter />
</ProtectedRoute>
}
>
<Route path="profile" element={<Profile />} />
</Route>
</Routes>
</div>
);
}
7.2 实现 ProtectedRoute
js
// components/ProtectedRoute.jsx
import { Navigate } from 'react-router-dom';
function ProtectedRoute({ children }) {
const isAuthenticated = localStorage.getItem('token'); // 模拟认证
return isAuthenticated ? children : <Navigate to="/login" replace />;
}
7.3 用户中心
js
// pages/UserCenter.jsx
import { Link, Outlet } from 'react-router-dom';
function UserCenter() {
return (
<div>
<h1>用户中心</h1>
<nav>
<Link to="/user/profile">编辑资料</Link>
</nav>
<Outlet /> {/* 渲染子路由 */}
</div>
);
}
7.4 个人资料页
jsx
// pages/Profile.jsx
function Profile() {
return <h2>个人资料页面</h2>;
}
7.5 更新导航栏
js
// components/Navbar.jsx
import { NavLink } from 'react-router-dom';
function Navbar() {
return (
<nav>
<NavLink to="/" className={({ isActive }) => (isActive ? 'active' : '')}>
首页
</NavLink>{' '}
|{' '}
<NavLink
to="/products"
className={({ isActive }) => (isActive ? 'active' : '')}
>
商品列表
</NavLink>{' '}
|{' '}
<NavLink
to="/user"
className={({ isActive }) => (isActive ? 'active' : '')}
>
用户中心
</NavLink>
</nav>
);
}
测试步骤
- 未登录时(
localStorage.getItem('token')
为 null),访问/user
会跳转到/login
。 - 模拟登录(
localStorage.setItem('token', '123')
),即可访问/user
和/user/profile
。
8. 总结与进阶建议
React Router v6 是一个功能强大且灵活的路由管理工具,通过 <Routes>
、<Route>
、<Link>
等组件以及 useNavigate
、useParams
等 Hook,开发者可以轻松实现多页面应用的导航和权限控制。本文通过电商网站案例和用户中心练习,展示了 React Router v6 的核心功能及其新特性(如 <Outlet>
和简化的 API)。
进阶建议
- 数据加载器(Data Loaders):探索 React Router v6 的数据加载功能,提升页面加载性能。
- 状态管理集成:结合 Redux 或 Context API,实现更复杂的路由逻辑。
希望这篇教程能为你的 React 开发之旅提供坚实的基础!如果有任何问题,欢迎交流。
以下是一个完整的 React Router v6 电商网站示例代码,供参考:
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>电商网站 - React Router v6</title>
<script src="https://cdn.jsdelivr.net/npm/react@18/umd/react.development.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-dom@18/umd/react-dom.development.js"></script>
<script src="https://cdn.jsdelivr.net/npm/react-router-dom@6/dist/umd/react-router-dom.development.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@babel/standalone/babel.min.js"></script>
<style>
nav { margin-bottom: 20px; }
.active { color: red; font-weight: bold; }
button { margin: 0 5px; }
</style>
</head>
<body>
<div id="root"></div>
<script type="text/babel">
const { BrowserRouter, Routes, Route, Link, NavLink, useParams, useNavigate, useSearchParams, Outlet, Navigate } = ReactRouterDOM;
function ProtectedRoute({ children }) {
const isAuthenticated = localStorage.getItem('token');
return isAuthenticated ? children : <Navigate to="/login" replace />;
}
function Navbar() {
return (
<nav>
<NavLink to="/" className={({ isActive }) => (isActive ? 'active' : '')}>首页</NavLink> |{' '}
<NavLink to="/products" className={({ isActive }) => (isActive ? 'active' : '')}>商品列表</NavLink> |{' '}
<NavLink to="/user" className={({ isActive }) => (isActive ? 'active' : '')}>用户中心</NavLink>
</nav>
);
}
function Home() {
return <h1>欢迎来到电商网站</h1>;
}
function ProductList() {
const [searchParams, setSearchParams] = useSearchParams();
const category = searchParams.get('category');
const products = [
{ id: 1, name: '手机', category: 'electronics' },
{ id: 2, name: '书籍', category: 'books' },
];
const filteredProducts = category ? products.filter(p => p.category === category) : products;
return (
<div>
<h1>商品列表 - {category || '全部'}</h1>
<button onClick={() => setSearchParams({ category: 'electronics' })}>电子产品</button>
<button onClick={() => setSearchParams({ category: 'books' })}>书籍</button>
<button onClick={() => setSearchParams({})}>全部</button>
<ul>
{filteredProducts.map(product => (
<li key={product.id}>
<Link to={`/product/${product.id}`}>{product.name}</Link>
</li>
))}
</ul>
</div>
);
}
function ProductDetail() {
const { id } = useParams();
const navigate = useNavigate();
const products = {
1: { name: '手机', price: 2999 },
2: { name: '书籍', price: 59 },
};
const product = products[id] || { name: '未知商品', price: 0 };
return (
<div>
<h1>{product.name}</h1>
<p>价格: ¥{product.price}</p>
<button onClick={() => navigate('/products')}>返回列表</button>
</div>
);
}
function UserCenter() {
return (
<div>
<h1>用户中心</h1>
<nav>
<Link to="/user/profile">编辑资料</Link>
</nav>
<Outlet />
</div>
);
}
function Profile() {
return <h2>个人资料页面</h2>;
}
function Login() {
const navigate = useNavigate();
const handleLogin = () => {
localStorage.setItem('token', '123');
navigate('/user');
};
return <button onClick={handleLogin}>登录</button>;
}
function App() {
return (
<div>
<Navbar />
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products" element={<ProductList />} />
<Route path="/product/:id" element={<ProductDetail />} />
<Route path="/login" element={<Login />} />
<Route path="/user" element={<ProtectedRoute><UserCenter /></ProtectedRoute>}>
<Route path="profile" element={<Profile />} />
</Route>
</Routes>
</div>
);
}
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
</script>
</body>
</html>
运行这段代码,你将得到一个完整的电商网站示例,包含所有功能点。祝你学习愉快!