React Router 教程
项目概述
React Router 是 React 的官方路由库,提供了声明式的路由解决方案,用于构建单页应用(SPA)。它支持客户端路由、嵌套路由、参数路由、重定向、导航守卫等功能,使开发者能够轻松管理应用的导航状态和 URL 结构。
- 项目链接:https://github.com/remix-run/react-router
- 官方网站:https://reactrouter.com/
- GitHub Stars:50k+
- 适用环境:React 项目、单页应用
安装设置
1. 安装 React Router
# 安装核心包
npm install react-router-dom
# 或者使用 yarn
yarn add react-router-dom2. 基本配置
在应用的根组件中设置路由配置:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter } from 'react-router-dom';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);核心功能
1. 基本路由
import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
function Home() {
return <h1>首页</h1>;
}
function About() {
return <h1>关于我们</h1>;
}
function Contact() {
return <h1>联系我们</h1>;
}
function App() {
return (
<div>
{/* 导航链接 */}
<nav>
<Link to="/">首页</Link> |
<Link to="/about">关于我们</Link> |
<Link to="/contact">联系我们</Link>
</nav>
{/* 路由配置 */}
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</div>
);
}
export default App;2. 嵌套路由
import React from 'react';
import { Routes, Route, Link, Outlet } from 'react-router-dom';
function Dashboard() {
return (
<div>
<h1>仪表盘</h1>
<nav>
<Link to="profile">个人资料</Link> |
<Link to="settings">设置</Link> |
<Link to="activity">活动记录</Link>
</nav>
{/* 子路由的渲染位置 */}
<Outlet />
</div>
);
}
function Profile() {
return <h2>个人资料</h2>;
}
function Settings() {
return <h2>设置</h2>;
}
function Activity() {
return <h2>活动记录</h2>;
}
function App() {
return (
<div>
<nav>
<Link to="/">首页</Link> |
<Link to="/dashboard">仪表盘</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/dashboard" element={<Dashboard />}>
{/* 嵌套路由 */}
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
<Route path="activity" element={<Activity />} />
</Route>
</Routes>
</div>
);
}3. 参数路由
import React from 'react';
import { Routes, Route, Link, useParams } from 'react-router-dom';
function Users() {
return (
<div>
<h1>用户列表</h1>
<ul>
<li><Link to="/users/1">用户 1</Link></li>
<li><Link to="/users/2">用户 2</Link></li>
<li><Link to="/users/3">用户 3</Link></li>
</ul>
</div>
);
}
function UserDetail() {
// 获取路由参数
const { id } = useParams();
return <h1>用户详情 - ID: {id}</h1>;
}
function App() {
return (
<div>
<nav>
<Link to="/">首页</Link> |
<Link to="/users">用户列表</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/users" element={<Users />} />
{/* 参数路由 */}
<Route path="/users/:id" element={<UserDetail />} />
</Routes>
</div>
);
}4. 导航
使用 Link 组件
import { Link, NavLink } from 'react-router-dom';
// 基本链接
<Link to="/about">关于我们</Link>
// 带状态的链接
<Link to="/dashboard" state={{ from: 'home' }}>仪表盘</Link>
// 活跃状态的链接
<NavLink to="/" className={({ isActive }) => isActive ? 'active' : ''}>
首页
</NavLink>
// 带样式的活跃链接
<NavLink
to="/about"
style={({ isActive }) => ({
color: isActive ? 'red' : 'blue'
})}
>
关于我们
</NavLink>使用编程式导航
import { useNavigate } from 'react-router-dom';
function LoginPage() {
const navigate = useNavigate();
const handleLogin = () => {
// 登录成功后导航
navigate('/dashboard');
// 带替换(不会在历史记录中添加新条目)
navigate('/dashboard', { replace: true });
// 带状态
navigate('/dashboard', {
state: { token: 'abc123' },
replace: true
});
// 后退
navigate(-1);
// 前进
navigate(1);
};
return (
<div>
<h1>登录</h1>
<button onClick={handleLogin}>登录</button>
</div>
);
}5. 重定向
import { Routes, Route, Navigate } from 'react-router-dom';
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
{/* 重定向 */}
<Route path="/old-path" element={<Navigate to="/new-path" replace />} />
{/* 带状态的重定向 */}
<Route
path="/login"
element={
isLoggedIn ? (
<Navigate to="/dashboard" state={{ from: '/login' }} />
) : (
<LoginPage />
)
}
/>
</Routes>
);
}6. 404 页面
import { Routes, Route } from 'react-router-dom';
function NotFound() {
return (
<div>
<h1>404</h1>
<p>页面不存在</p>
<Link to="/">返回首页</Link>
</div>
);
}
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* 404 页面 */}
<Route path="*" element={<NotFound />} />
</Routes>
);
}高级功能
1. 路由守卫
使用自定义组件实现路由守卫
import { Navigate, useLocation } from 'react-router-dom';
function RequireAuth({ children }) {
const location = useLocation();
const isLoggedIn = checkIfUserIsLoggedIn(); // 自定义登录检查
if (!isLoggedIn) {
// 重定向到登录页,并保存当前位置以便登录后返回
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<LoginPage />} />
{/* 受保护的路由 */}
<Route
path="/dashboard"
element={
<RequireAuth>
<Dashboard />
</RequireAuth>
}
/>
</Routes>
);
}2. 路由加载器
使用 React Router v6.4+ 的加载器功能
import { createBrowserRouter, RouterProvider, useLoaderData } from 'react-router-dom';
// 加载器函数
async function userLoader({ params }) {
const response = await fetch(`/api/users/${params.id}`);
if (!response.ok) {
throw new Error('Failed to load user');
}
return response.json();
}
// 使用加载器数据
function UserDetail() {
const user = useLoaderData();
return (
<div>
<h1>{user.name}</h1>
<p>{user.email}</p>
</div>
);
}
// 路由配置
const router = createBrowserRouter([
{
path: '/',
element: <App />,
children: [
{
path: 'users/:id',
element: <UserDetail />,
loader: userLoader
}
]
}
]);
// 渲染路由
function Root() {
return <RouterProvider router={router} />;
}3. 路由操作器
使用 React Router v6.4+ 的操作器功能
import { createBrowserRouter, RouterProvider, useActionData, Form } from 'react-router-dom';
// 操作器函数
async function loginAction({ request }) {
const formData = await request.formData();
const credentials = {
username: formData.get('username'),
password: formData.get('password')
};
const response = await fetch('/api/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(credentials)
});
if (!response.ok) {
return { error: '登录失败' };
}
// 登录成功,存储 token
const data = await response.json();
localStorage.setItem('token', data.token);
return null;
}
// 登录页面
function LoginPage() {
const actionData = useActionData();
return (
<div>
<h1>登录</h1>
{actionData?.error && <p>{actionData.error}</p>}
<Form method="post">
<input type="text" name="username" placeholder="用户名" />
<input type="password" name="password" placeholder="密码" />
<button type="submit">登录</button>
</Form>
</div>
);
}
// 路由配置
const router = createBrowserRouter([
{
path: '/',
element: <App />,
children: [
{
path: 'login',
element: <LoginPage />,
action: loginAction
}
]
}
]);4. 嵌套布局
import { createBrowserRouter, RouterProvider, Outlet } from 'react-router-dom';
// 根布局
function RootLayout() {
return (
<div>
<header>
<h1>我的应用</h1>
</header>
<main>
<Outlet />
</main>
<footer>
<p>© 2024 我的应用</p>
</footer>
</div>
);
}
// 仪表盘布局
function DashboardLayout() {
return (
<div>
<nav>
<Link to="profile">个人资料</Link>
<Link to="settings">设置</Link>
</nav>
<Outlet />
</div>
);
}
// 路由配置
const router = createBrowserRouter([
{
path: '/',
element: <RootLayout />,
children: [
{
index: true,
element: <Home />
},
{
path: 'dashboard',
element: <DashboardLayout />,
children: [
{
index: true,
element: <DashboardHome />
},
{
path: 'profile',
element: <Profile />
},
{
path: 'settings',
element: <Settings />
}
]
}
]
}
]);5. 路由状态管理
import { useLocation, useNavigate } from 'react-router-dom';
function CheckoutPage() {
const location = useLocation();
const navigate = useNavigate();
// 获取之前页面传递的状态
const cartItems = location.state?.cartItems || [];
const handleCheckout = () => {
// 处理结账逻辑
// 成功后导航到确认页,并传递订单信息
navigate('/confirmation', {
state: {
orderId: '12345',
items: cartItems
}
});
};
return (
<div>
<h1>结账</h1>
<div>商品数量: {cartItems.length}</div>
<button onClick={handleCheckout}>确认结账</button>
</div>
);
}
function ConfirmationPage() {
const location = useLocation();
const orderId = location.state?.orderId;
if (!orderId) {
return <Navigate to="/checkout" replace />;
}
return (
<div>
<h1>订单确认</h1>
<p>您的订单 {orderId} 已成功提交!</p>
</div>
);
}实际应用场景
1. 认证系统
import { Routes, Route, Navigate, useLocation, useNavigate } from 'react-router-dom';
// 认证上下文
import { useAuth } from './AuthContext';
// 登录页面
function LoginPage() {
const navigate = useNavigate();
const location = useLocation();
const { login } = useAuth();
// 从 location.state 中获取重定向路径
const from = location.state?.from?.pathname || '/';
const handleSubmit = async (credentials) => {
const success = await login(credentials);
if (success) {
// 登录成功后重定向到之前的页面
navigate(from, { replace: true });
}
};
return (
<div>
<h1>登录</h1>
<LoginForm onSubmit={handleSubmit} />
</div>
);
}
// 注册页面
function RegisterPage() {
const navigate = useNavigate();
const { register } = useAuth();
const handleSubmit = async (userData) => {
const success = await register(userData);
if (success) {
navigate('/dashboard');
}
};
return (
<div>
<h1>注册</h1>
<RegisterForm onSubmit={handleSubmit} />
</div>
);
}
// 认证路由守卫
function ProtectedRoute({ children }) {
const { isAuthenticated } = useAuth();
const location = useLocation();
if (!isAuthenticated) {
// 未认证时重定向到登录页
return <Navigate to="/login" state={{ from: location }} replace />;
}
return children;
}
// 应用路由
function App() {
return (
<Routes>
<Route path="/" element={<Home />} />
<Route path="/login" element={<LoginPage />} />
<Route path="/register" element={<RegisterPage />} />
<Route
path="/dashboard"
element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
}
/>
<Route
path="/profile"
element={
<ProtectedRoute>
<Profile />
</ProtectedRoute>
}
/>
</Routes>
);
}2. 电商应用
import { Routes, Route, Link, useParams, useNavigate } from 'react-router-dom';
// 商品列表
function ProductList() {
const navigate = useNavigate();
const handleProductClick = (productId) => {
navigate(`/products/${productId}`);
};
return (
<div>
<h1>商品列表</h1>
<div className="product-grid">
{products.map(product => (
<div
key={product.id}
className="product-card"
onClick={() => handleProductClick(product.id)}
>
<h3>{product.name}</h3>
<p>${product.price}</p>
</div>
))}
</div>
</div>
);
}
// 商品详情
function ProductDetail() {
const { id } = useParams();
const navigate = useNavigate();
const product = products.find(p => p.id === id);
if (!product) {
return <div>商品不存在</div>;
}
const handleAddToCart = () => {
addToCart(product);
navigate('/cart');
};
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>${product.price}</p>
<button onClick={handleAddToCart}>加入购物车</button>
<Link to="/products">返回商品列表</Link>
</div>
);
}
// 购物车
function Cart() {
const navigate = useNavigate();
const cartItems = getCartItems();
const handleCheckout = () => {
navigate('/checkout');
};
return (
<div>
<h1>购物车</h1>
{cartItems.length === 0 ? (
<p>购物车为空</p>
) : (
<>
{cartItems.map(item => (
<div key={item.id}>
<h3>{item.name}</h3>
<p>${item.price} × {item.quantity}</p>
</div>
))}
<div>总计: ${calculateTotal()}</div>
<button onClick={handleCheckout}>去结账</button>
</>
)}
<Link to="/products">继续购物</Link>
</div>
);
}
// 结账
function Checkout() {
const navigate = useNavigate();
const handleSubmitOrder = () => {
// 处理订单提交
navigate('/confirmation');
};
return (
<div>
<h1>结账</h1>
<CheckoutForm onSubmit={handleSubmitOrder} />
</div>
);
}
// 订单确认
function Confirmation() {
return (
<div>
<h1>订单确认</h1>
<p>您的订单已成功提交!</p>
<Link to="/products">继续购物</Link>
</div>
);
}
// 应用路由
function App() {
return (
<div>
<nav>
<Link to="/">首页</Link> |
<Link to="/products">商品</Link> |
<Link to="/cart">购物车</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/products" element={<ProductList />} />
<Route path="/products/:id" element={<ProductDetail />} />
<Route path="/cart" element={<Cart />} />
<Route path="/checkout" element={<Checkout />} />
<Route path="/confirmation" element={<Confirmation />} />
</Routes>
</div>
);
}3. 管理后台
import { createBrowserRouter, RouterProvider, Outlet, NavLink } from 'react-router-dom';
// 后台布局
function AdminLayout() {
return (
<div className="admin-panel">
<aside>
<h2>管理后台</h2>
<nav>
<NavLink to="dashboard">仪表盘</NavLink>
<NavLink to="users">用户管理</NavLink>
<NavLink to="products">商品管理</NavLink>
<NavLink to="orders">订单管理</NavLink>
</nav>
</aside>
<main>
<Outlet />
</main>
</div>
);
}
// 仪表盘
function AdminDashboard() {
return (
<div>
<h1>仪表盘</h1>
<div className="stats">
<div>总用户: 1000</div>
<div>总商品: 500</div>
<div>总订单: 2000</div>
</div>
</div>
);
}
// 用户管理
function UserManagement() {
return (
<div>
<h1>用户管理</h1>
{/* 用户列表和管理功能 */}
</div>
);
}
// 商品管理
function ProductManagement() {
return (
<div>
<h1>商品管理</h1>
{/* 商品列表和管理功能 */}
</div>
);
}
// 订单管理
function OrderManagement() {
return (
<div>
<h1>订单管理</h1>
{/* 订单列表和管理功能 */}
</div>
);
}
// 路由配置
const router = createBrowserRouter([
{
path: '/',
element: <App />,
children: [
{
path: 'admin',
element: <AdminLayout />,
children: [
{
index: true,
element: <AdminDashboard />
},
{
path: 'users',
element: <UserManagement />
},
{
path: 'products',
element: <ProductManagement />
},
{
path: 'orders',
element: <OrderManagement />
}
]
}
]
}
]);
// 渲染应用
function Root() {
return <RouterProvider router={router} />;
}代码优化建议
- 路由配置分离:将路由配置分离到单独的文件中,提高代码可维护性
- 使用懒加载:对于大型应用,使用 React.lazy() 和 Suspense 实现路由懒加载
- 路由守卫抽象:将认证逻辑抽象为可重用的路由守卫组件
- 嵌套路由合理使用:利用嵌套路由和布局组件减少代码重复
- 参数验证:对路由参数进行验证,确保数据安全性
- 错误边界:为路由组件添加错误边界,提高应用稳定性
- 导航状态管理:合理使用 location.state 传递临时数据
- SEO 考虑:对于需要 SEO 的页面,考虑使用服务端渲染或静态生成
- 性能优化:
- 避免在路由组件中进行昂贵的计算
- 使用 memo 缓存路由组件
- 合理使用 useMemo 和 useCallback
- 代码组织:
- 按功能模块组织路由和组件
- 使用一致的命名规范
- 添加适当的注释