React从基础入门到高级实战:React 核心技术 - React Router:路由管理
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,只需运行以下命令:
npm install react-router-dom@6
注意:确保指定版本
@6
,以避免安装旧版本或其他不兼容的版本。
2.2 基本配置
React Router v6 使用 <BrowserRouter>
作为路由容器,通常在应用的根组件中进行配置。<BrowserRouter>
利用 HTML5 History API 管理路由,支持干净的 URL(如 /about
而不是 #/about
)。
代码示例:
// 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
属性指定渲染的组件。
代码示例:
// 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
可以渲染同一个组件,但显示不同的商品详情。
代码示例:
// 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 获取参数:
// 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 来处理查询参数。
代码示例:
// 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>
组件用于创建导航链接,点击时跳转到指定路径,避免页面刷新。
代码示例:
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>
的增强版,可以根据当前路径动态调整样式,例如高亮当前导航项。
代码示例:
import { NavLink } from 'react-router-dom';function Navbar() {return (<nav><NavLinkto="/"className={({ isActive }) => (isActive ? 'active' : '')}>首页</NavLink>{' '}|{' '}<NavLinkto="/about"className={({ isActive }) => (isActive ? 'active' : '')}>关于我们</NavLink></nav>);
}
className
:接受一个函数,参数isActive
表示当前路径是否匹配。- 示例 CSS:
.active {color: red;font-weight: bold;
}
4.3 useNavigate
Hook
useNavigate
Hook 允许在组件内部以编程方式导航,适用于事件驱动的场景(如按钮点击、表单提交)。
代码示例:
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 嵌套路由
嵌套路由允许在父路由中定义子路由,常用于实现包含公共布局的页面(如导航栏 + 主内容)。
代码示例:
// 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 没有内置的守卫组件,但可以通过自定义组件实现。
实现方式:
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;
}// 使用
<Routepath="/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 路由配置
// 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 导航栏
// components/Navbar.jsx
import { NavLink } from 'react-router-dom';function Navbar() {return (<nav><NavLink to="/" className={({ isActive }) => (isActive ? 'active' : '')}>首页</NavLink>{' '}|{' '}<NavLinkto="/products"className={({ isActive }) => (isActive ? 'active' : '')}>商品列表</NavLink></nav>);
}
6.4 首页
// pages/Home.jsx
function Home() {return <h1>欢迎来到电商网站</h1>;
}
6.5 商品列表
// 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 商品详情
// 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 更新路由配置
// 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 />} /><Routepath="/user"element={<ProtectedRoute><UserCenter /></ProtectedRoute>}><Route path="profile" element={<Profile />} /></Route></Routes></div>);
}
7.2 实现 ProtectedRoute
// 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 用户中心
// 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 个人资料页
// pages/Profile.jsx
function Profile() {return <h2>个人资料页面</h2>;
}
7.5 更新导航栏
// components/Navbar.jsx
import { NavLink } from 'react-router-dom';function Navbar() {return (<nav><NavLink to="/" className={({ isActive }) => (isActive ? 'active' : '')}>首页</NavLink>{' '}|{' '}<NavLinkto="/products"className={({ isActive }) => (isActive ? 'active' : '')}>商品列表</NavLink>{' '}|{' '}<NavLinkto="/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 电商网站示例代码,供参考:
<!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>
运行这段代码,你将得到一个完整的电商网站示例,包含所有功能点。祝你学习愉快!