React Router DOM 教程
项目概述
React Router DOM 是 React Router 的 DOM 绑定,专门为 Web 浏览器环境设计。它提供了声明式的路由解决方案,支持客户端路由、嵌套路由、参数路由、导航守卫等功能,与 React 生态系统深度集成,使构建单页应用(SPA)变得更加简单和直观。
- 项目链接:https://github.com/remix-run/react-router
- 官方网站:https://reactrouter.com/
- GitHub Stars:50k+
- 适用环境:React 项目、Web 浏览器
安装设置
1. 安装 React Router DOM
# 使用 npm
npm install react-router-dom
# 使用 yarn
yarn add react-router-dom
# 使用 pnpm
pnpm 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>
);3. 路由配置
// App.jsx
import React from 'react';
import { Routes, Route, Link } from 'react-router-dom';
import Home from './pages/Home';
import About from './pages/About';
import Contact from './pages/Contact';
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;核心功能
1. 路由组件
BrowserRouter
使用 HTML5 History API 实现的路由器,提供干净的 URL(无哈希)。
import { BrowserRouter } from 'react-router-dom';
function Root() {
return (
<BrowserRouter>
<App />
</BrowserRouter>
);
}HashRouter
使用 URL 哈希实现的路由器,适合无法配置服务器的环境。
import { HashRouter } from 'react-router-dom';
function Root() {
return (
<HashRouter>
<App />
</HashRouter>
);
}MemoryRouter
使用内存存储历史记录的路由器,适合测试和非浏览器环境。
import { MemoryRouter } from 'react-router-dom';
function TestWrapper() {
return (
<MemoryRouter initialEntries={['/']}>
<App />
</MemoryRouter>
);
}2. 导航组件
Link
创建导航链接,不会触发页面刷新。
import { Link } from 'react-router-dom';
function Navigation() {
return (
<nav>
{/* 基本链接 */}
<Link to="/">首页</Link>
{/* 带状态的链接 */}
<Link to="/dashboard" state={{ from: 'home' }}>仪表盘</Link>
{/* 带查询参数 */}
<Link to="/search?q=react">搜索 React</Link>
</nav>
);
}NavLink
带活跃状态的链接,可根据路由状态应用不同样式。
import { NavLink } from 'react-router-dom';
function Navigation() {
return (
<nav>
{/* 带类名的活跃链接 */}
<NavLink
to="/"
className={({ isActive }) => isActive ? 'active-link' : 'link'}
>
首页
</NavLink>
{/* 带样式的活跃链接 */}
<NavLink
to="/about"
style={({ isActive }) => ({
color: isActive ? 'red' : 'blue',
fontWeight: isActive ? 'bold' : 'normal'
})}
>
关于我们
</NavLink>
{/* 精确匹配 */}
<NavLink to="/" end>首页</NavLink>
</nav>
);
}3. 路由渲染
Routes 和 Route
Routes 组件用于包裹路由配置,Route 组件定义单个路由规则。
import { Routes, Route } from 'react-router-dom';
function App() {
return (
<Routes>
{/* 基本路由 */}
<Route path="/" element={<Home />} />
{/* 参数路由 */}
<Route path="/users/:id" element={<UserDetail />} />
{/* 嵌套路由 */}
<Route path="/dashboard" element={<Dashboard />}>
<Route index element={<DashboardHome />} />
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
{/* 404 页面 */}
<Route path="*" element={<NotFound />} />
</Routes>
);
}Outlet
用于渲染嵌套路由的组件。
import { Outlet, Link } from 'react-router-dom';
function Dashboard() {
return (
<div>
<h1>仪表盘</h1>
<nav>
<Link to="">首页</Link> |
<Link to="profile">个人资料</Link> |
<Link to="settings">设置</Link>
</nav>
{/* 嵌套路由将在这里渲染 */}
<Outlet />
</div>
);
}4. Hooks API
useNavigate
用于编程式导航。
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>
);
}useRoute
用于获取当前路由信息。
import { useRoute } from 'react-router-dom';
function UserDetail() {
const route = useRoute();
const userId = route.params.id;
const query = route.query;
return (
<div>
<h1>用户详情 - ID: {userId}</h1>
{query.ref && <p>来源: {query.ref}</p>}
</div>
);
}useLocation
用于获取当前位置信息。
import { useLocation } from 'react-router-dom';
function Page() {
const location = useLocation();
// 获取路径
const path = location.pathname;
// 获取查询参数
const search = location.search;
// 获取哈希
const hash = location.hash;
// 获取状态
const state = location.state;
return (
<div>
<h1>当前路径: {path}</h1>
{state && <p>状态: {JSON.stringify(state)}</p>}
</div>
);
}useMatch
用于匹配指定路径。
import { useMatch } from 'react-router-dom';
function Navigation() {
const homeMatch = useMatch('/');
const aboutMatch = useMatch('/about');
return (
<nav>
<a
href="/"
className={homeMatch ? 'active' : ''}
>
首页
</a>
<a
href="/about"
className={aboutMatch ? 'active' : ''}
>
关于我们
</a>
</nav>
);
}5. 导航守卫
使用高阶组件实现
import { Navigate, useLocation } from 'react-router-dom';
function RequireAuth({ children }) {
const location = useLocation();
const isLoggedIn = localStorage.getItem('token');
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>
);
}高级功能
1. 懒加载路由
使用 React.lazy() 和 Suspense 实现路由组件的懒加载,减小初始包大小。
import React, { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
// 懒加载组件
const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));
const Dashboard = lazy(() => import('./pages/Dashboard'));
// 加载中组件
function Loading() {
return <div>加载中...</div>;
}
function App() {
return (
<Suspense fallback={<Loading />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/dashboard" element={<Dashboard />} />
</Routes>
</Suspense>
);
}2. 嵌套布局
使用嵌套路由和布局组件实现复杂的页面结构。
import { Routes, Route, Outlet } from 'react-router-dom';
// 根布局
function RootLayout() {
return (
<div className="app">
<header>
<h1>我的应用</h1>
</header>
<main>
<Outlet />
</main>
<footer>
<p>© 2024 我的应用</p>
</footer>
</div>
);
}
// 认证布局
function AuthLayout() {
return (
<div className="auth-layout">
<Outlet />
</div>
);
}
// 仪表盘布局
function DashboardLayout() {
return (
<div className="dashboard">
<aside>
<nav>
<Link to="">首页</Link>
<Link to="profile">个人资料</Link>
<Link to="settings">设置</Link>
</nav>
</aside>
<main>
<Outlet />
</main>
</div>
);
}
function App() {
return (
<Routes>
{/* 根布局 */}
<Route element={<RootLayout />}>
{/* 公共路由 */}
<Route index element={<Home />} />
<Route path="about" element={<About />} />
{/* 认证路由 */}
<Route element={<AuthLayout />}>
<Route path="login" element={<LoginPage />} />
<Route path="register" element={<RegisterPage />} />
</Route>
{/* 受保护的仪表盘路由 */}
<Route
path="dashboard"
element={
<RequireAuth>
<DashboardLayout />
</RequireAuth>
}
>
<Route index element={<DashboardHome />} />
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Route>
</Routes>
);
}3. 路由状态管理
使用 location.state 在路由之间传递数据,无需依赖全局状态管理。
import { useLocation, useNavigate } from 'react-router-dom';
function ProductList() {
const navigate = useNavigate();
const handleProductClick = (product) => {
// 传递产品数据到详情页
navigate(`/product/${product.id}`, {
state: { product }
});
};
return (
<div>
{products.map(product => (
<div
key={product.id}
onClick={() => handleProductClick(product)}
>
<h3>{product.name}</h3>
<p>${product.price}</p>
</div>
))}
</div>
);
}
function ProductDetail() {
const location = useLocation();
const product = location.state?.product;
if (!product) {
// 如果没有产品数据,重定向回列表页
return <Navigate to="/products" replace />;
}
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<p>${product.price}</p>
</div>
);
}4. 搜索参数管理
使用 useSearchParams 钩子管理 URL 搜索参数。
import { useSearchParams } from 'react-router-dom';
function SearchPage() {
// 获取和设置搜索参数
const [searchParams, setSearchParams] = useSearchParams();
// 获取当前搜索词
const query = searchParams.get('q') || '';
// 获取当前页码
const page = parseInt(searchParams.get('page')) || 1;
const handleSearch = (e) => {
e.preventDefault();
const formData = new FormData(e.target);
const newQuery = formData.get('query');
// 更新搜索参数
setSearchParams({ q: newQuery, page: 1 });
};
const handlePageChange = (newPage) => {
// 保持搜索词,只更新页码
setSearchParams({ q: query, page: newPage });
};
return (
<div>
<form onSubmit={handleSearch}>
<input
type="text"
name="query"
defaultValue={query}
placeholder="搜索..."
/>
<button type="submit">搜索</button>
</form>
<div>
<h2>搜索结果: {query}</h2>
<p>第 {page} 页</p>
{/* 分页按钮 */}
<button onClick={() => handlePageChange(page - 1)} disabled={page === 1}>
上一页
</button>
<button onClick={() => handlePageChange(page + 1)}>
下一页
</button>
</div>
</div>
);
}5. 程序化导航
使用 useNavigate 钩子实现复杂的导航逻辑。
import { useNavigate, useLocation } from 'react-router-dom';
function LoginPage() {
const navigate = useNavigate();
const location = useLocation();
// 从 location.state 获取重定向路径
const from = location.state?.from?.pathname || '/';
const handleLogin = async (credentials) => {
try {
// 登录逻辑
const token = await login(credentials);
localStorage.setItem('token', token);
// 登录成功后重定向到之前的页面
navigate(from, { replace: true });
} catch (error) {
console.error('登录失败:', error);
}
};
return (
<div>
<h1>登录</h1>
<LoginForm onSubmit={handleLogin} />
</div>
);
}
function LogoutButton() {
const navigate = useNavigate();
const handleLogout = () => {
// 清除登录状态
localStorage.removeItem('token');
// 重定向到登录页
navigate('/login', { replace: true });
};
return <button onClick={handleLogout}>退出登录</button>;
}实际应用场景
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, useLocation } from 'react-router-dom';
// 商品列表
function ProductList() {
const navigate = useNavigate();
const handleProductClick = (productId) => {
navigate(`/product/${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 location = useLocation();
const product = location.state?.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="/product/:id" element={<ProductDetail />} />
<Route path="/cart" element={<Cart />} />
<Route path="/checkout" element={<Checkout />} />
<Route path="/confirmation" element={<Confirmation />} />
</Routes>
</div>
);
}3. 博客系统
import { Routes, Route, Link, useParams, useNavigate } from 'react-router-dom';
// 博客首页
function BlogHome() {
return (
<div>
<h1>我的博客</h1>
<div className="blog-posts">
{posts.map(post => (
<div key={post.id} className="blog-post-preview">
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
<Link to={`/blog/${post.slug}`}>阅读更多</Link>
</div>
))}
</div>
</div>
);
}
// 博客文章详情
function BlogPost() {
const { slug } = useParams();
const post = posts.find(p => p.slug === slug);
if (!post) {
return <div>文章不存在</div>;
}
return (
<div className="blog-post">
<h1>{post.title}</h1>
<p className="post-meta">{post.date} · {post.author}</p>
<div className="post-content">{post.content}</div>
<Link to="/blog">返回博客首页</Link>
</div>
);
}
// 博客管理
function BlogAdmin() {
const navigate = useNavigate();
const handleCreatePost = () => {
navigate('/blog/admin/create');
};
const handleEditPost = (postId) => {
navigate(`/blog/admin/edit/${postId}`);
};
return (
<div>
<h1>博客管理</h1>
<button onClick={handleCreatePost}>创建新文章</button>
<div className="post-list">
{posts.map(post => (
<div key={post.id}>
<h3>{post.title}</h3>
<button onClick={() => handleEditPost(post.id)}>编辑</button>
</div>
))}
</div>
</div>
);
}
// 应用路由
function App() {
return (
<div>
<nav>
<Link to="/">首页</Link> |
<Link to="/blog">博客</Link> |
<Link to="/blog/admin">博客管理</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/blog" element={<BlogHome />} />
<Route path="/blog/:slug" element={<BlogPost />} />
<Route path="/blog/admin" element={<BlogAdmin />} />
<Route path="/blog/admin/create" element={<CreatePost />} />
<Route path="/blog/admin/edit/:id" element={<EditPost />} />
</Routes>
</div>
);
}代码优化建议
- 路由配置分离:将路由配置分离到单独的文件中,按功能模块组织
- 使用懒加载:对大型路由组件使用 React.lazy() 和 Suspense 实现懒加载
- 导航守卫抽象:将认证逻辑抽象为可重用的高阶组件或钩子
- 嵌套路由合理使用:利用嵌套路由和布局组件减少代码重复
- 参数验证:对路由参数进行验证,确保数据安全性
- 错误边界:为路由组件添加错误边界,提高应用稳定性
- 导航状态管理:合理使用 location.state 传递临时数据,减少全局状态依赖
- SEO 考虑:对于需要 SEO 的页面,考虑使用服务端渲染或静态生成
- 性能优化:
- 避免在路由组件中进行昂贵的计算
- 使用 memo 缓存路由组件
- 合理使用 useMemo 和 useCallback
- 优化导航守卫的执行时间
- 代码组织:
- 按功能模块组织路由和组件
- 使用一致的命名规范
- 添加适当的注释
- 使用 TypeScript 提高类型安全性