React路由与导航详解
第6章 路由与导航详解
🎯 章节学习目标
本章将深入讲解React应用中的路由与导航技术,从基础的React Router使用到高级的路由管理模式。通过学习本章,你将掌握单页应用的路由设计、权限控制、性能优化等核心技术。
📋 学习任务清单
✅ 基础概念与安装
✅ 路由配置与导航
✅ 动态路由与参数
✅ 嵌套路由与布局
✅ 权限控制与路由守卫
✅ 状态管理与数据获取
✅ 性能优化与懒加载
✅ 高级模式与最佳实践
🚀 核心知识点概览
- React Router生态系统 - 理解react-router和react-router-dom的关系
- 路由配置模式 - 掌握声明式和编程式路由配置
- 导航生命周期 - 理解路由切换的完整流程
- 参数处理机制 - 掌握路径参数和查询参数的处理
- 嵌套路由设计 - 学会复杂应用的路由架构设计
- 权限控制体系 - 实现细粒度的路由权限管理
- 性能优化策略 - 优化路由相关的应用性能
- 调试与测试 - 掌握路由相关的调试和测试技术
💡 学习建议
- 循序渐进:从基础概念开始,逐步深入到高级特性
- 实践为主:每个知识点都要通过代码实践来巩固
- 案例驱动:通过完整的项目案例来理解整体设计
- 性能意识:在学习和实践中始终关注性能影响
- 最佳实践:学习业界成熟的路由设计模式
6.1 React Router 基础概念
6.1.1 单页应用路由原理
传统多页应用 vs 单页应用:
// 传统多页应用(MPA - Multi-Page Application)
// 特点:
// 1. 每个页面都是独立的HTML文件
// 2. 页面跳转需要完整的页面刷新
// 3. 服务器返回完整的HTML文档
// 4. URL变化对应不同的页面文件
// 单页应用(SPA - Single-Page Application)
// 特点:
// 1. 只有一个主HTML文件
// 2. 页面切换通过JavaScript动态更新内容
// 3. 无需完整页面刷新,用户体验更好
// 4. 路由在前端控制,服务器提供API数据
// 路由的核心职责
const routeResponsibilities = {
// 1. URL解析:将浏览器URL映射到对应的组件
parseURL: '将 /users/123 解析为用户详情页面',
// 2. 组件渲染:根据URL渲染对应的React组件
renderComponent: '渲染 UserDetail 组件并传入ID参数',
// 3. 历史管理:管理浏览器的前进后退功能
manageHistory: '维护路由历史栈,支持浏览器导航',
// 4. 状态同步:保持URL与应用状态的同步
syncState: 'URL参数与应用状态保持一致'
}
前端路由的实现原理:
// 前端路由的两种主要实现方式
// 1. Hash Router(基于URL Hash)
// URL格式:http://example.com/#/users/123
// 特点:兼容性好,不需要服务器配置
// 缺点:URL不够美观,对SEO不友好
const hashRouterImplementation = {
// 监听hash变化
hashChange: 'window.addEventListener("hashchange", handler)',
// 获取当前路由
getCurrentPath: 'window.location.hash.slice(1)',
// 编程式导航
navigate: 'window.location.hash = "#/new/path"'
}
// 2. History Router(基于History API)
// URL格式:http://example.com/users/123
// 特点:URL美观,SEO友好
// 缺点:需要服务器配置支持
const historyRouterImplementation = {
// History API
pushState: 'history.pushState(state, title, url)',
replaceState: 'history.replaceState(state, title, url)',
// 监听popstate事件
popStateListener: 'window.addEventListener("popstate", handler)',
// 获取当前路径
getCurrentPath: 'window.location.pathname'
}
React Router 生态系统:
// React Router 包结构
const reactRouterPackages = {
// 核心包 - 提供路由核心功能
'react-router': {
description: 'React Router的核心逻辑',
components: ['Router', 'Routes', 'Route', 'useRoutes'],
usage: '用于任何React环境的路由功能'
},
// Web包 - 包含Web特定组件
'react-router-dom': {
description: 'Web环境下的React Router',
components: ['BrowserRouter', 'HashRouter', 'Link', 'NavLink'],
usage: '用于浏览器环境的路由功能'
},
// Native包 - React Native环境
'react-router-native': {
description: 'React Native环境的路由',
components: ['NativeRouter', 'Link', 'useLinking'],
usage: '用于移动应用的路由功能'
}
}
6.1.2 React Router v6 新特性
React Router v6 vs v5 主要变化:
// v5 语法 vs v6 语法对比
// 1. 路由配置语法变化
// v5 写法
import { BrowserRouter, Route, Switch } from 'react-router-dom'
function AppV5() {
return (
<BrowserRouter>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/users/:id" component={UserDetail} />
<Route component={NotFound} />
</Switch>
</BrowserRouter>
)
}
// v6 写法
import { BrowserRouter, Routes, Route } from 'react-router-dom'
function AppV6() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/users/:id" element={<UserDetail />} />
<Route path="*" element={<NotFound />} />
</Routes>
</BrowserRouter>
)
}
// 2. 嵌套路由配置变化
// v5 写法
function AppV5() {
return (
<BrowserRouter>
<Switch>
<Route path="/users">
<Users>
<Switch>
<Route exact path="/users" component={UserList} />
<Route path="/users/:id" component={UserDetail} />
</Switch>
</Users>
</Route>
</Switch>
</BrowserRouter>
)
}
// v6 写法 - 更简洁的嵌套路由
function AppV6() {
return (
<BrowserRouter>
<Routes>
<Route path="/users" element={<Users />}>
<Route index element={<UserList />} />
<Route path=":id" element={<UserDetail />} />
</Route>
</Routes>
</BrowserRouter>
)
}
v6 的核心新特性:
// 1. 新的路由匹配算法
// v6 使用新的排名算法,更加直观和可预测
const routeMatchingV6 = {
// 特性1:自动排序
autoSorting: '根据路由配置自动排序,无需手动排序',
// 特性2:更精确的匹配
preciseMatching: '路由匹配更加精确,减少意外匹配',
// 特性3:相对路径支持
relativePaths: '支持相对路径配置,更灵活的路由组织'
}
// 2. Outlet 组件
// 用于渲染嵌套路由的子路由内容
import { Outlet } from 'react-router-dom'
function UsersLayout() {
return (
<div>
<h1>用户管理</h1>
<nav>
<Link to="list">用户列表</Link>
<Link to="create">创建用户</Link>
</nav>
{/* 子路由内容将在这里渲染 */}
<Outlet />
</div>
)
}
// 3. useNavigate Hook
// 替代 v5 中的 useHistory
import { useNavigate } from 'react-router-dom'
function UserComponent() {
const navigate = useNavigate()
const handleNavigate = () => {
// 编程式导航
navigate('/users/123')
// 替换当前历史记录
navigate('/users/123', { replace: true })
// 前进/后退
navigate(1) // 前进一页
navigate(-1) // 后退一页
// 传递状态
navigate('/users/123', {
state: { from: 'dashboard' }
})
}
return <button onClick={handleNavigate}>查看用户</button>
}
// 4. useLocation Hook
// 获取当前路由信息
import { useLocation } from 'react-router-dom'
function LocationInfo() {
const location = useLocation()
console.log('当前路径:', location.pathname)
console.log('查询参数:', location.search)
console.log('Hash:', location.hash)
console.log('状态:', location.state)
return (
<div>
<p>当前路径: {location.pathname}</p>
<p>查询参数: {location.search}</p>
</div>
)
}
6.1.3 安装和基础配置
安装 React Router:
# 使用 npm 安装
npm install react-router-dom
# 使用 yarn 安装
yarn add react-router-dom
# 安装 TypeScript 类型定义(如果使用 TypeScript)
npm install @types/react-router-dom --save-dev
# 验证安装
npm list react-router-dom
项目结构建议:
// 推荐的路由相关文件结构
src/
├── components/ # 通用组件
│ ├── Layout/ # 布局组件
│ └── Navigation/ # 导航组件
├── pages/ # 页面组件
│ ├── Home/
│ ├── About/
│ ├── Users/
│ │ ├── UserList.jsx
│ │ ├── UserDetail.jsx
│ │ └── UserEdit.jsx
│ └── NotFound/
├── router/ # 路由配置
│ ├── index.jsx # 主路由文件
│ ├── protected.jsx # 路由守卫
│ └── routes.js # 路由配置数据
├── hooks/ # 自定义 Hooks
│ ├── useAuth.js
│ └── usePermissions.js
└── utils/ # 工具函数
├── auth.js
└── permissions.js
基础路由配置示例:
// src/router/index.jsx - 主路由配置文件
import React, { Suspense } from 'react'
import { createBrowserRouter, RouterProvider } from 'react-router-dom'
import { Spin } from 'antd'
// 懒加载页面组件
const Home = React.lazy(() => import('../pages/Home'))
const About = React.lazy(() => import('../pages/About'))
const Users = React.lazy(() => import('../pages/Users'))
const UserDetail = React.lazy(() => import('../pages/Users/UserDetail'))
const NotFound = React.lazy(() => import('../pages/NotFound'))
// 加载组件
const LoadingSpinner = () => (
<div style={{
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
height: '200px'
}}>
<Spin size="large" />
</div>
)
// 错误边界组件
class ErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false }
}
static getDerivedStateFromError(error) {
return { hasError: true }
}
componentDidCatch(error, errorInfo) {
console.error('路由错误:', error, errorInfo)
}
render() {
if (this.state.hasError) {
return <div>页面加载失败,请刷新重试</div>
}
return this.props.children
}
}
// 路由配置
const routerConfig = createBrowserRouter([
{
path: '/',
element: (
<Suspense fallback={<LoadingSpinner />}>
<ErrorBoundary>
<Home />
</ErrorBoundary>
</Suspense>
),
errorElement: <NotFound />,
meta: {
title: '首页',
description: '欢迎来到我们的应用'
}
},
{
path: '/about',
element: (
<Suspense fallback={<LoadingSpinner />}>
<About />
</Suspense>
),
meta: {
title: '关于我们',
description: '了解更多关于我们的信息'
}
},
{
path: '/users',
element: (
<Suspense fallback={<LoadingSpinner />}>
<Users />
</Suspense>
),
children: [
{
index: true,
element: <div>请选择一个用户</div>
},
{
path: ':userId',
element: <UserDetail />,
loader: async ({ params }) => {
// 路由级别的数据加载
const response = await fetch(`/api/users/${params.userId}`)
if (!response.ok) {
throw new Response('用户未找到', { status: 404 })
}
return response.json()
}
}
]
},
{
path: '*',
element: <NotFound />
}
])
// 主应用组件
function App() {
return <RouterProvider router={routerConfig} />
}
export default App
在入口文件中使用:
// src/index.jsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './router'
import './index.css'
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
)
---
### 6.2 路由配置和导航组件详解
#### 6.2.1 BrowserRouter vs HashRouter
**BrowserRouter 使用场景:**
```jsx
// BrowserRouter - 基于 History API
// 适用场景:
// 1. 生产环境应用
// 2. 需要 SEO 优化的应用
// 3. URL 美观性要求高的应用
// 4. 需要支持浏览器前进后退的应用
import { BrowserRouter } from 'react-router-dom'
function App() {
return (
<BrowserRouter>
{/* 路由配置 */}
</BrowserRouter>
)
}
// BrowserRouter 配置选项
<BrowserRouter
basename="/app" // 应用的基础路径
future={{ // 启用未来版本的特性
v7_startTransition: true,
v7_relativeSplatPath: true
}}
>
{/* 应用内容 */}
</BrowserRouter>
// 实际应用示例
function ProductionApp() {
return (
<BrowserRouter basename="/my-app">
<Routes>
<Route path="/" element={<Home />} /> // /my-app/
<Route path="/users" element={<Users />} /> // /my-app/users
<Route path="/about" element={<About />} /> // /my-app/about
</Routes>
</BrowserRouter>
)
}
HashRouter 使用场景:
// HashRouter - 基于 URL Hash
// 适用场景:
// 1. 开发环境或测试环境
// 2. 静态文件托管(如 GitHub Pages)
// 3. 无法配置服务器的环境
// 4. 快速原型开发
import { HashRouter } from 'react-router-dom'
function App() {
return (
<HashRouter>
{/* 路由配置 */}
</HashRouter>
)
}
// HashRouter 示例
function StaticApp() {
return (
<HashRouter>
<Routes>
<Route path="/" element={<Home />} /> // #/
<Route path="/users" element={<Users />} /> // #/users
<Route path="/about" element={<About />} /> // #/about
</Routes>
</HashRouter>
)
}
// HashRouter 高级配置
<HashRouter
hashType="slash" // Hash 类型: 'slash' | 'noslash' | 'hashbang'
window={window} // 自定义 window 对象
>
{/* 应用内容 */}
</HashRouter>
路由器选择指南:
// 路由器选择决策树
const routerSelection = {
// 检查部署环境
deploymentEnvironment: {
canConfigureServer: '使用 BrowserRouter',
staticHostingOnly: '使用 HashRouter',
githubPages: '使用 HashRouter',
customServer: '使用 BrowserRouter'
},
// SEO 考虑
seoRequirements: {
needSEO: 'BrowserRouter(更好的 SEO)',
noSEO: '两者皆可,HashRouter 更简单'
},
// URL 美观性
urlAppearance: {
needCleanURL: 'BrowserRouter (/users/123)',
hashAcceptable: 'HashRouter (/#/users/123)'
}
}
// 动态选择路由器的示例
import { BrowserRouter, HashRouter } from 'react-router-dom'
function RouterProvider({ children }) {
const isProduction = process.env.NODE_ENV === 'production'
const isStaticHosting = process.env.REACT_APP_STATIC_HOSTING === 'true'
const Router = isStaticHosting ? HashRouter : BrowserRouter
return (
<Router>
{children}
</Router>
)
}
6.2.2 Routes 和 Route 组件详解
Routes 组件:
// Routes 是 React Router v6 的核心组件
// 负责路由匹配和渲染
import { Routes, Route } from 'react-router-dom'
function App() {
return (
<Routes>
{/* 路由配置 */}
</Routes>
)
}
// Routes 的核心特性
const routesFeatures = {
// 1. 自动路由排序
autoSorting: '根据路由配置的顺序和特异性自动排序',
// 2. 独占匹配
exclusiveMatching: '只渲染第一个匹配的路由',
// 3. 嵌套支持
nestingSupport: '支持无限层级的路由嵌套',
// 4. 相对路径
relativePaths: '支持相对路径配置'
}
Route 组件详解:
// Route 组件的完整属性
<Route
path="/users/:id" // 路由路径(支持参数和通配符)
element={<UserDetail />} // 要渲染的组件
index={false} // 是否为索引路由
caseSensitive={false} // 是否区分大小写
loader={userLoader} // 数据加载函数
action={userAction} // 表单提交处理函数
errorElement={<ErrorBoundary />} // 错误边界组件
id="user-route" // 路由唯一标识
handle={ // 额外的路由数据
title: '用户详情',
breadcrumb: '用户 > 详情'
}
>
{/* 嵌套路由 */}
<Route path="edit" element={<UserEdit />} />
<Route path="settings" element={<UserSettings />} />
</Route>
// 路由路径语法详解
const routePathSyntax = {
// 静态路径
staticPath: '/about',
// 动态参数
dynamicParam: '/users/:userId',
// 多个参数
multipleParams: '/orgs/:orgId/users/:userId',
// 可选参数(使用 ?)
optionalParam: '/users/:userId?',
// 通配符
wildcard: '/files/*',
// 通配符参数
wildcardParam: '/files/*',
// 重复参数
repeatParam: '/products/:ids+',
// 重复可选参数
repeatOptionalParam: '/products/:ids*'
}
高级路由配置示例:
// 完整的路由配置示例
import { Routes, Route, Navigate } from 'react-router-dom'
import { lazy, Suspense } from 'react'
// 懒加载组件
const Dashboard = lazy(() => import('./pages/Dashboard'))
const Products = lazy(() => import('./pages/Products'))
const ProductDetail = lazy(() => import('./pages/ProductDetail'))
const Profile = lazy(() => import('./pages/Profile'))
const Settings = lazy(() => import('./pages/Settings'))
const NotFound = lazy(() => import('./pages/NotFound'))
// 加载组件
const PageLoader = () => (
<div className="page-loader">
<div className="spinner" />
<p>加载中...</p>
</div>
)
function AppRoutes() {
return (
<Suspense fallback={<PageLoader />}>
<Routes>
{/* 重定向路由 */}
<Route path="/" element={<Navigate to="/dashboard" replace />} />
{/* 仪表板路由 */}
<Route path="/dashboard" element={<Dashboard />} />
{/* 产品管理路由组 */}
<Route path="/products" element={<Products />}>
<Route index element={<div>选择一个产品查看详情</div>} />
<Route
path=":productId"
element={<ProductDetail />}
loader={async ({ params }) => {
const response = await fetch(`/api/products/${params.productId}`)
return response.json()
}}
/>
<Route
path=":productId/edit"
element={<ProductEdit />}
action={async ({ request, params }) => {
const formData = await request.formData()
return updateProduct(params.productId, Object.fromEntries(formData))
}}
/>
</Route>
{/* 用户相关路由 */}
<Route path="/profile" element={<Profile />} />
<Route path="/settings" element={<Settings />}>
<Route index element={<SettingsGeneral />} />
<Route path="account" element={<SettingsAccount />} />
<Route path="security" element={<SettingsSecurity />} />
</Route>
{/* 404 页面 */}
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
)
}
// 路由元数据处理
function useRouteMeta(route) {
const { handle } = route
return {
title: handle?.title || '默认标题',
breadcrumb: handle?.breadcrumb || '面包屑',
permissions: handle?.permissions || []
}
}
6.2.3 Link 和 NavLink 组件
Link 组件详解:
// Link 组件 - 基础导航组件
import { Link } from 'react-router-dom'
function Navigation() {
return (
<nav>
{/* 基础用法 */}
<Link to="/">首页</Link>
<Link to="/about">关于我们</Link>
<Link to="/contact">联系我们</Link>
{/* 动态路径 */}
<Link to={`/users/${userId}`}>用户详情</Link>
{/* 相对路径 */}
<Link to="settings">设置</Link>
{/* 查询参数 */}
<Link to="/search?q=react&type=tutorial">搜索</Link>
{/* Hash */}
<Link to="/about#team">团队介绍</Link>
</nav>
)
}
// Link 组件的完整属性
<Link
to="/about" // 目标路径
replace={false} // 是否替换当前历史记录
state={{ from: 'home' }} // 传递的状态数据
reloadDocument={false} // 是否强制刷新页面
preventScrollReset={false} // 是否阻止滚动重置
relative="route" // 路径解析方式: 'route' | 'path'
>
链接文本
</Link>
// 高级 Link 用法示例
function AdvancedLinks() {
const location = useLocation()
const navigate = useNavigate()
return (
<div>
{/* 带状态的导航 */}
<Link
to="/profile"
state={{
from: location.pathname,
timestamp: Date.now()
}}
>
个人资料
</Link>
{/* 替换当前页面 */}
<Link to="/login" replace={true}>
登录(替换当前页面)
</Link>
{/* 强制页面刷新 */}
<Link to="/external-page" reloadDocument={true}>
外部页面
</Link>
{/* 条件导航 */}
<Link
to={isAuthenticated ? "/dashboard" : "/login"}
state={{ intended: "/dashboard" }}
>
{isAuthenticated ? "进入控制台" : "登录"}
</Link>
</div>
)
}
NavLink 组件详解:
// NavLink - 带活动状态的链接组件
import { NavLink } from 'react-router-dom'
function ActiveNavigation() {
// 自定义活动状态样式
const navLinkStyles = ({ isActive }) => ({
fontWeight: isActive ? 'bold' : 'normal',
color: isActive ? '#1976d2' : '#666',
textDecoration: isActive ? 'none' : 'underline'
})
return (
<nav>
<NavLink to="/" style={navLinkStyles}>
首页
</NavLink>
<NavLink to="/products" style={navLinkStyles}>
产品
</NavLink>
<NavLink to="/about" style={navLinkStyles}>
关于我们
</NavLink>
{/* 使用 className */}
<NavLink
to="/contact"
className={({ isActive }) =>
isActive ? 'nav-link active' : 'nav-link'
}
>
联系我们
</NavLink>
</nav>
)
}
// NavLink 高级配置
<NavLink
to="/admin"
className={({ isActive, isPending }) => `
nav-link
${isActive ? 'active' : ''}
${isPending ? 'pending' : ''}
`}
style={({ isActive, isPending }) => ({
color: isActive ? 'white' : '#333',
backgroundColor: isActive ? '#1976d2' : 'transparent',
opacity: isPending ? 0.7 : 1
})}
end={false} // 是否匹配子路径
caseSensitive={false} // 是否区分大小写
>
管理后台
</NavLink>
导航组件组合示例:
// 完整的导航组件示例
import { Link, NavLink, useLocation, useMatch } from 'react-router-dom'
import { useState } from 'react'
function NavigationBar() {
const location = useLocation()
const [mobileMenuOpen, setMobileMenuOpen] = useState(false)
// 导航配置
const navItems = [
{ path: '/', label: '首页', icon: '🏠' },
{ path: '/products', label: '产品', icon: '📦' },
{ path: '/services', label: '服务', icon: '🔧' },
{ path: '/about', label: '关于', icon: 'ℹ️' },
{ path: '/contact', label: '联系', icon: '📧' }
]
// 检查当前路径是否匹配
const isCurrentPath = (path) => {
if (path === '/') {
return location.pathname === '/'
}
return location.pathname.startsWith(path)
}
return (
<header className="app-header">
{/* Logo */}
<Link to="/" className="logo">
<span> MyApp </span>
</Link>
{/* 桌面导航 */}
<nav className="desktop-nav">
{navItems.map((item) => (
<NavLink
key={item.path}
to={item.path}
className={({ isActive }) =>
`nav-item ${isActive ? 'active' : ''}`
}
end={item.path === '/'}
>
<span className="nav-icon">{item.icon}</span>
<span className="nav-label">{item.label}</span>
</NavLink>
))}
</nav>
{/* 移动端菜单 */}
<button
className="mobile-menu-toggle"
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
>
☰
</button>
{mobileMenuOpen && (
<nav className="mobile-nav">
{navItems.map((item) => (
<Link
key={item.path}
to={item.path}
className={`mobile-nav-item ${isCurrentPath(item.path) ? 'active' : ''}`}
onClick={() => setMobileMenuOpen(false)}
>
<span className="nav-icon">{item.icon}</span>
<span className="nav-label">{item.label}</span>
</Link>
))}
</nav>
)}
{/* 用户菜单 */}
<div className="user-menu">
<Link to="/profile" className="user-avatar">
<img src="/avatar.jpg" alt="用户头像" />
</Link>
</div>
</header>
)
}
// 面包屑导航组件
function Breadcrumb() {
const location = useLocation()
const pathnames = location.pathname.split('/').filter(x => x)
const breadcrumbMap = {
'': '首页',
'products': '产品',
'services': '服务',
'about': '关于我们',
'contact': '联系我们'
}
return (
<nav className="breadcrumb">
<Link to="/">首页</Link>
{pathnames.map((name, index) => {
const routeTo = `/${pathnames.slice(0, index + 1).join('/')}`
const isLast = index === pathnames.length - 1
return (
<span key={name}>
<span className="separator"> / </span>
{isLast ? (
<span className="current">
{breadcrumbMap[name] || name}
</span>
) : (
<Link to={routeTo}>
{breadcrumbMap[name] || name}
</Link>
)}
</span>
)
})}
</nav>
)
}
6.2.4 编程式导航
useNavigate Hook 详解:
// useNavigate - 编程式导航的核心 Hook
import { useNavigate, useLocation } from 'react-router-dom'
function NavigationExample() {
const navigate = useNavigate()
const location = useLocation()
// 基础导航
const goToHome = () => {
navigate('/')
}
// 带参数的导航
const goToUser = (userId) => {
navigate(`/users/${userId}`)
}
// 替换当前页面
const replaceWithLogin = () => {
navigate('/login', { replace: true })
}
// 前进/后退
const goBack = () => {
navigate(-1)
}
const goForward = () => {
navigate(1)
}
// 带状态的导航
const goToProfile = () => {
navigate('/profile', {
state: {
from: location.pathname,
timestamp: Date.now()
}
})
}
// 相对导航
const goRelative = () => {
navigate('../sibling') // 上一级
navigate('./child') // 当前路径下
}
return (
<div>
<button onClick={goToHome}>首页</button>
<button onClick={() => goToUser(123)}>用户详情</button>
<button onClick={replaceWithLogin}>登录(替换)</button>
<button onClick={goBack}>后退</button>
<button onClick={goForward}>前进</button>
<button onClick={goToProfile}>个人资料</button>
</div>
)
}
// navigate 函数的完整选项
navigate(to, options)
// to 参数类型
const toTypes = {
string: '/users/123', // 字符串路径
number: -1, // 数字(历史记录导航)
object: { // 路径对象
pathname: '/users/123',
search: '?tab=profile',
hash: '#section2'
}
}
// options 选项
const navigateOptions = {
replace: false, // 是否替换当前历史记录
state: {}, // 传递的状态数据
preventScrollReset: false, // 是否阻止滚动重置
relative: 'route' // 路径解析方式
}
高级编程式导航场景:
// 复杂的导航逻辑封装
import { useCallback } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
function useCustomNavigation() {
const navigate = useNavigate()
const location = useLocation()
// 认证导航
const navigateWithAuth = useCallback((to, options = {}) => {
if (!isAuthenticated()) {
// 保存目标路径,登录后跳转
navigate('/login', {
state: {
from: to,
returnTo: location.pathname
},
replace: true
})
return false
}
navigate(to, options)
return true
}, [navigate, location])
// 确认导航
const navigateWithConfirmation = useCallback((to, message = '确定要离开当前页面吗?') => {
if (window.confirm(message)) {
navigate(to)
return true
}
return false
}, [navigate])
// 条件导航
const conditionalNavigate = useCallback((routes, defaultRoute = '/') => {
for (const route of routes) {
if (route.condition) {
navigate(route.path, route.options)
return route.path
}
}
navigate(defaultRoute)
}, [navigate])
// 延迟导航
const delayedNavigate = useCallback((to, delay = 1000) => {
setTimeout(() => {
navigate(to)
}, delay)
}, [navigate])
return {
navigateWithAuth,
navigateWithConfirmation,
conditionalNavigate,
delayedNavigate
}
}
// 使用示例
function FormPage() {
const { navigateWithConfirmation } = useCustomNavigation()
const [isDirty, setIsDirty] = useState(false)
const handleNavigate = (to) => {
if (isDirty) {
navigateWithConfirmation(
to,
'您有未保存的更改,确定要离开吗?'
)
} else {
navigate(to)
}
}
return (
<form>
{/* 表单内容 */}
<button type="button" onClick={() => handleNavigate('/')}>
返回首页
</button>
</form>
)
}
// 路由守卫导航
function ProtectedRoute({ children, requiredPermission }) {
const navigate = useNavigate()
const { user, hasPermission } = useAuth()
if (!user) {
useEffect(() => {
navigate('/login', {
state: { from: window.location.pathname }
})
}, [navigate])
return null
}
if (requiredPermission && !hasPermission(requiredPermission)) {
useEffect(() => {
navigate('/unauthorized')
}, [navigate])
return null
}
return children
}
// 批量导航操作
function useBatchNavigation() {
const navigate = useNavigate()
const openInNewTab = useCallback((to) => {
window.open(to, '_blank')
}, [])
const openMultipleTabs = useCallback((paths) => {
paths.forEach((path, index) => {
setTimeout(() => {
window.open(path, '_blank')
}, index * 100) // 间隔100ms打开
})
}, [])
const navigateSequence = useCallback((paths, delay = 1000) => {
paths.forEach((path, index) => {
setTimeout(() => {
navigate(path, { replace: index > 0 })
}, index * delay)
})
}, [navigate])
return {
openInNewTab,
openMultipleTabs,
navigateSequence
}
}
导航事件处理:
// 导航事件监听和处理
import { useEffect, useCallback } from 'react'
import { useNavigate, useLocation, useBlocker } from 'react-router-dom'
function NavigationEvents() {
const navigate = useNavigate()
const location = useLocation()
// 页面离开确认
const blocker = useBlocker(
({ currentLocation, nextLocation }) =>
isFormDirty && currentLocation.pathname !== nextLocation.pathname
)
// 路由变化监听
useEffect(() => {
console.log('路由变化:', location.pathname)
// 更新页面标题
document.title = getPageTitle(location.pathname)
// 发送页面访问统计
trackPageView(location.pathname)
}, [location])
// 确认离开处理
const handleLeaveConfirmation = useCallback(() => {
if (blocker.state === 'blocked') {
if (window.confirm('您有未保存的更改,确定要离开吗?')) {
blocker.proceed()
} else {
blocker.reset()
}
}
}, [blocker])
return (
<div>
{blocker.state === 'blocked' && (
<div className="leave-confirmation">
<p>您有未保存的更改</p>
<button onClick={handleLeaveConfirmation}>确认离开</button>
<button onClick={() => blocker.reset()}>继续编辑</button>
</div>
)}
</div>
)
}
// 智能导航 Hook
function useSmartNavigation() {
const navigate = useNavigate()
const location = useLocation()
// 智能返回
const smartGoBack = useCallback((fallbackPath = '/') => {
if (window.history.length > 1) {
navigate(-1)
} else {
navigate(fallbackPath, { replace: true })
}
}, [navigate])
// 保存状态导航
const saveAndNavigate = useCallback((to, data, options = {}) => {
// 保存当前状态
saveCurrentState(data)
// 导航到目标页面
navigate(to, {
state: { savedData: data },
...options
})
}, [navigate])
// 恢复状态导航
const restoreAndNavigate = useCallback((to, key, options = {}) => {
const savedData = restoreSavedState(key)
navigate(to, {
state: { restoredData: savedData },
...options
})
}, [navigate])
return {
smartGoBack,
saveAndNavigate,
restoreAndNavigate
}
}
}
---
### 6.3 动态路由和参数处理
#### 6.3.1 路径参数详解
**基础路径参数:**
```jsx
// 路径参数 - URL中的动态部分
// 语法::paramName
import { Routes, Route, useParams } from 'react-router-dom'
// 路由配置
function App() {
return (
<Routes>
{/* 单个参数 */}
<Route path="/users/:userId" element={<UserProfile />} />
{/* 多个参数 */}
<Route path="/orgs/:orgId/users/:userId" element={<UserDetail />} />
{/* 可选参数(使用 ?) */}
<Route path="/posts/:slug?" element={<PostView />} />
{/* 通配符参数 */}
<Route path="/files/*" element={<FileExplorer />} />
</Routes>
)
}
// 获取路径参数
function UserProfile() {
const { userId } = useParams()
return (
<div>
<h1>用户资料</h1>
<p>用户ID: {userId}</p>
</div>
)
}
// 多参数示例
function UserDetail() {
const { orgId, userId } = useParams()
return (
<div>
<h1>用户详情</h1>
<p>组织ID: {orgId}</p>
<p>用户ID: {userId}</p>
</div>
)
}
// 参数类型转换
function UserDetail() {
const { userId } = useParams()
// 将字符串参数转换为数字
const userIdNumber = parseInt(userId, 10)
// 验证参数
if (isNaN(userIdNumber)) {
return <div>无效的用户ID</div>
}
// 获取用户数据
const user = getUserById(userIdNumber)
if (!user) {
return <div>用户不存在</div>
}
return <div>{user.name}</div>
}
高级参数模式:
// 重复参数(使用 +)- 匹配一个或多个
<Route path="/tags/:tags+" element={<TagPage />} />
// 重复可选参数(使用 *)- 匹配零个或多个
<Route path="/filters/:filters*" element={<FilterPage />} />
// 实际应用示例
function TagPage() {
const { tags } = useParams()
const tagList = tags.split(',')
return (
<div>
<h1>标签: {tagList.join(', ')}</h1>
{/* 渲染相关内容 */}
</div>
)
}
function FilterPage() {
const { filters } = useParams()
if (!filters) {
return <div>无筛选条件</div>
}
const filterList = filters.split('&')
const filterObject = filterList.reduce((acc, filter) => {
const [key, value] = filter.split('=')
acc[key] = value
return acc
}, {})
return (
<div>
<h1>筛选条件</h1>
<pre>{JSON.stringify(filterObject, null, 2)}</pre>
</div>
)
}
// 复杂的参数验证
function useValidatedParams(schema) {
const params = useParams()
const [validatedParams, setValidatedParams] = useState(null)
const [errors, setErrors] = useState({})
useEffect(() => {
const validation = validateSchema(params, schema)
if (validation.isValid) {
setValidatedParams(validation.data)
setErrors({})
} else {
setErrors(validation.errors)
setValidatedParams(null)
}
}, [params, schema])
return { params: validatedParams, errors }
}
// 使用示例
function ProductDetail() {
const { params, errors } = useValidatedParams({
productId: {
type: 'number',
required: true,
min: 1
},
variant: {
type: 'string',
enum: ['basic', 'premium', 'pro'],
default: 'basic'
}
})
if (Object.keys(errors).length > 0) {
return <div>参数错误: {JSON.stringify(errors)}</div>
}
if (!params) {
return <div>验证中...</div>
}
return <div>产品ID: {params.productId}, 版本: {params.variant}</div>
}
6.3.2 查询参数处理
URLSearchParams 基础:
// 查询参数 - URL中 ? 后面的键值对
// 示例: /search?q=react&type=tutorial&page=2
import { useSearchParams } from 'react-router-dom'
function SearchPage() {
// useSearchParams Hook 返回 [searchParams, setSearchParams]
const [searchParams, setSearchParams] = useSearchParams()
// 获取查询参数
const query = searchParams.get('q')
const type = searchParams.get('type')
const page = parseInt(searchParams.get('page') || '1', 10)
// 检查参数是否存在
const hasFilters = searchParams.has('type')
// 获取所有参数
const allParams = Object.fromEntries(searchParams)
// 更新查询参数
const updateSearch = (newQuery) => {
setSearchParams(prev => {
prev.set('q', newQuery)
prev.delete('page') // 重置页码
return prev
})
}
const setFilters = (filters) => {
setSearchParams(prev => {
Object.entries(filters).forEach(([key, value]) => {
if (value) {
prev.set(key, value)
} else {
prev.delete(key)
}
})
return prev
})
}
return (
<div>
<h1>搜索结果</h1>
<p>关键词: {query}</p>
<p>类型: {type}</p>
<p>页码: {page}</p>
<SearchForm onSearch={updateSearch} />
<FilterForm onFilter={setFilters} />
</div>
)
}
// 搜索表单组件
function SearchForm({ onSearch }) {
const [query, setQuery] = useState('')
const handleSubmit = (e) => {
e.preventDefault()
onSearch(query)
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="搜索..."
/>
<button type="submit">搜索</button>
</form>
)
}
高级查询参数管理:
// 自定义 Hook - 管理复杂的查询参数
function useQueryParams(initialParams = {}) {
const [searchParams, setSearchParams] = useSearchParams()
// 将 URLSearchParams 转换为对象
const getQueryObject = useCallback(() => {
const params = {}
searchParams.forEach((value, key) => {
// 尝试转换数字
if (!isNaN(value) && !isNaN(parseFloat(value))) {
params[key] = parseFloat(value)
} else if (value === 'true') {
params[key] = true
} else if (value === 'false') {
params[key] = false
} else {
params[key] = value
}
})
return { ...initialParams, ...params }
}, [searchParams, initialParams])
// 更新单个参数
const updateParam = useCallback((key, value) => {
setSearchParams(prev => {
if (value === undefined || value === null || value === '') {
prev.delete(key)
} else {
prev.set(key, String(value))
}
return prev
})
}, [setSearchParams])
// 批量更新参数
const updateParams = useCallback((updates) => {
setSearchParams(prev => {
Object.entries(updates).forEach(([key, value]) => {
if (value === undefined || value === null || value === '') {
prev.delete(key)
} else {
prev.set(key, String(value))
}
})
return prev
})
}, [setSearchParams])
// 重置所有参数
const resetParams = useCallback(() => {
setSearchParams(new URLSearchParams())
}, [setSearchParams])
// 清理参数(移除空值)
const cleanParams = useCallback(() => {
setSearchParams(prev => {
const cleaned = new URLSearchParams()
prev.forEach((value, key) => {
if (value && value !== '') {
cleaned.set(key, value)
}
})
return cleaned
})
}, [setSearchParams])
return {
query: getQueryObject(),
searchParams,
updateParam,
updateParams,
resetParams,
cleanParams
}
}
// 使用示例
function ProductList() {
const { query, updateParam, updateParams } = useQueryParams({
page: 1,
limit: 20,
sortBy: 'name',
sortOrder: 'asc'
})
// 分页处理
const handlePageChange = (newPage) => {
updateParam('page', newPage)
}
// 排序处理
const handleSort = (sortBy) => {
updateParams({
sortBy,
page: 1 // 重置到第一页
})
}
// 筛选处理
const handleFilter = (filters) => {
updateParams({
...filters,
page: 1 // 重置到第一页
})
}
return (
<div>
<ProductFilters onFilter={handleFilter} />
<ProductSorter onSort={handleSort} />
<ProductGrid
products={products}
page={query.page}
onPageChange={handlePageChange}
/>
</div>
)
}
// 查询参数持久化
function usePersistentQueryParams(key, initialParams = {}) {
const { query, updateParams, resetParams } = useQueryParams(initialParams)
// 从 localStorage 恢复参数
useEffect(() => {
const saved = localStorage.getItem(key)
if (saved) {
try {
const savedParams = JSON.parse(saved)
updateParams(savedParams)
} catch (error) {
console.warn('Failed to parse saved query params:', error)
}
}
}, [key, updateParams])
// 保存参数到 localStorage
const saveParams = useCallback((params) => {
localStorage.setItem(key, JSON.stringify(params))
}, [key])
// 包装 updateParams 以自动保存
const updateAndSave = useCallback((updates) => {
const newParams = { ...query, ...updates }
updateParams(updates)
saveParams(newParams)
}, [query, updateParams, saveParams])
// 包装 resetParams 以清除保存的数据
const resetAndClear = useCallback(() => {
resetParams()
localStorage.removeItem(key)
}, [resetParams, key])
return {
query,
updateParams: updateAndSave,
resetParams: resetAndClear
}
}
6.3.3 路由参数验证和转换
参数验证 Hook:
// 参数验证工具函数
const paramValidators = {
// 数字验证
number: (value, options = {}) => {
const num = Number(value)
const { min, max, integer = false } = options
if (isNaN(num)) return { valid: false, error: '必须是数字' }
if (integer && !Number.isInteger(num)) return { valid: false, error: '必须是整数' }
if (min !== undefined && num < min) return { valid: false, error: `不能小于${min}` }
if (max !== undefined && num > max) return { valid: false, error: `不能大于${max}` }
return { valid: true, value: num }
},
// 字符串验证
string: (value, options = {}) => {
const { minLength, maxLength, pattern } = options
if (typeof value !== 'string') value = String(value)
if (minLength && value.length < minLength) {
return { valid: false, error: `长度不能少于${minLength}` }
}
if (maxLength && value.length > maxLength) {
return { valid: false, error: `长度不能超过${maxLength}` }
}
if (pattern && !pattern.test(value)) {
return { valid: false, error: '格式不正确' }
}
return { valid: true, value }
},
// 枚举验证
enum: (value, options = {}) => {
const { values } = options
if (!values.includes(value)) {
return {
valid: false,
error: `必须是以下值之一: ${values.join(', ')}`
}
}
return { valid: true, value }
},
// UUID 验证
uuid: (value) => {
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
if (!uuidRegex.test(value)) {
return { valid: false, error: 'UUID 格式不正确' }
}
return { valid: true, value }
}
}
// 验证路由参数的 Hook
function useValidatedRouteParams(schema) {
const params = useParams()
const [validatedParams, setValidatedParams] = useState(null)
const [errors, setErrors] = useState({})
const [isValid, setIsValid] = useState(false)
useEffect(() => {
const validateParams = () => {
const results = {}
const newErrors = {}
let allValid = true
Object.entries(schema).forEach(([paramName, paramSchema]) => {
const value = params[paramName]
if (paramSchema.required && (value === undefined || value === null)) {
newErrors[paramName] = `${paramName} 是必需的`
allValid = false
return
}
if (value !== undefined && value !== null) {
const validator = paramValidators[paramSchema.type]
if (validator) {
const result = validator(value, paramSchema)
if (result.valid) {
results[paramName] = result.value
} else {
newErrors[paramName] = result.error
allValid = false
}
} else {
results[paramName] = value
}
} else if (paramSchema.default !== undefined) {
results[paramName] = paramSchema.default
}
})
setValidatedParams(results)
setErrors(newErrors)
setIsValid(allValid)
}
validateParams()
}, [params, schema])
return {
params: validatedParams,
errors,
isValid,
isLoading: validatedParams === null
}
}
// 使用示例
function UserDetail() {
const { params, errors, isValid, isLoading } = useValidatedRouteParams({
userId: {
type: 'number',
required: true,
min: 1,
integer: true
},
tab: {
type: 'enum',
values: ['profile', 'posts', 'comments', 'settings'],
default: 'profile'
}
})
if (isLoading) return <div>验证参数中...</div>
if (!isValid) {
return (
<div className="param-errors">
<h2>参数错误</h2>
{Object.entries(errors).map(([param, error]) => (
<div key={param} className="error">
{param}: {error}
</div>
))}
</div>
)
}
return (
<div>
<h1>用户详情</h1>
<p>用户ID: {params.userId}</p>
<p>当前标签: {params.tab}</p>
<UserProfileTabs
userId={params.userId}
activeTab={params.tab}
/>
</div>
)
}
查询参数验证:
// 查询参数验证 Hook
function useValidatedQueryParams(schema) {
const [searchParams] = useSearchParams()
const [validatedParams, setValidatedParams] = useState(null)
const [errors, setErrors] = useState({})
const [isValid, setIsValid] = useState(false)
useEffect(() => {
const validateQueryParams = () => {
const results = {}
const newErrors = {}
let allValid = true
Object.entries(schema).forEach(([paramName, paramSchema]) => {
const value = searchParams.get(paramName)
if (paramSchema.required && (value === null || value === '')) {
newErrors[paramName] = `${paramName} 是必需的`
allValid = false
return
}
if (value !== null && value !== '') {
const validator = paramValidators[paramSchema.type]
if (validator) {
const result = validator(value, paramSchema)
if (result.valid) {
results[paramName] = result.value
} else {
newErrors[paramName] = result.error
allValid = false
}
} else {
results[paramName] = value
}
} else if (paramSchema.default !== undefined) {
results[paramName] = paramSchema.default
}
})
setValidatedParams(results)
setErrors(newErrors)
setIsValid(allValid)
}
validateQueryParams()
}, [searchParams, schema])
return {
params: validatedParams,
errors,
isValid,
isLoading: validatedParams === null
}
}
// 复杂的搜索页面示例
function AdvancedSearch() {
const searchSchema = {
q: {
type: 'string',
required: true,
minLength: 2,
maxLength: 100
},
page: {
type: 'number',
min: 1,
default: 1
},
limit: {
type: 'number',
min: 1,
max: 100,
default: 20
},
sortBy: {
type: 'enum',
values: ['relevance', 'date', 'popularity', 'rating'],
default: 'relevance'
},
sortOrder: {
type: 'enum',
values: ['asc', 'desc'],
default: 'desc'
},
category: {
type: 'string',
pattern: /^[a-z0-9-]+$/
},
tags: {
type: 'string' // 将在组件中进一步处理为数组
}
}
const { params, errors, isValid, isLoading } = useValidatedQueryParams(searchSchema)
if (isLoading) return <div>加载中...</div>
if (!isValid) {
return <SearchErrors errors={errors} />
}
// 处理特殊的参数转换
const searchParams = {
...params,
tags: params.tags ? params.tags.split(',').filter(Boolean) : []
}
return (
<div>
<h1>高级搜索</h1>
<SearchForm initialValues={searchParams} />
<SearchResults params={searchParams} />
</div>
)
}
6.3.4 参数同步和状态管理
路由参数与应用状态同步:
// 将路由参数与组件状态同步的 Hook
function useParamSync(paramName, initialValue, options = {}) {
const { [paramName]: routeParam } = useParams()
const [value, setValue] = useState(initialValue)
const navigate = useNavigate()
const location = useLocation()
const {
syncToRoute = true,
syncFromRoute = true,
debounceMs = 300
} = options
// 从路由参数同步到状态
useEffect(() => {
if (syncFromRoute && routeParam !== undefined) {
setValue(routeParam)
}
}, [routeParam, syncFromRoute])
// 从状态同步到路由参数(防抖)
const debouncedNavigate = useMemo(
() => debounce((newValue) => {
if (!syncToRoute) return
const searchParams = new URLSearchParams(location.search)
if (newValue === initialValue || newValue === '') {
searchParams.delete(paramName)
} else {
searchParams.set(paramName, newValue)
}
const newSearch = searchParams.toString()
const newPath = newSearch
? `${location.pathname}?${newSearch}`
: location.pathname
navigate(newPath, { replace: true })
}, debounceMs),
[syncToRoute, initialValue, location.pathname, location.search, navigate, debounceMs]
)
// 包装 setValue 以同步到路由
const setSyncedValue = useCallback((newValue) => {
setValue(newValue)
debouncedNavigate(newValue)
}, [debouncedNavigate])
return [value, setSyncedValue]
}
// 防抖函数
function debounce(func, delay) {
let timeoutId
return (...args) => {
clearTimeout(timeoutId)
timeoutId = setTimeout(() => func.apply(this, args), delay)
}
}
// 使用示例 - 分页组件
function Pagination() {
const [page, setPage] = useParamSync('page', '1')
const totalPages = 10
const handlePageChange = (newPage) => {
setPage(String(newPage))
}
return (
<div className="pagination">
<button
onClick={() => handlePageChange(Math.max(1, parseInt(page) - 1))}
disabled={parseInt(page) <= 1}
>
上一页
</button>
<span className="page-info">
第 {page} 页,共 {totalPages} 页
</span>
<button
onClick={() => handlePageChange(Math.min(totalPages, parseInt(page) + 1))}
disabled={parseInt(page) >= totalPages}
>
下一页
</button>
</div>
)
}
// 复杂的表单同步
function useFormSync(schema) {
const location = useLocation()
const navigate = useNavigate()
const [formData, setFormData] = useState({})
// 从URL初始化表单数据
useEffect(() => {
const searchParams = new URLSearchParams(location.search)
const initialData = {}
Object.entries(schema).forEach(([key, config]) => {
const value = searchParams.get(key)
if (value !== null) {
// 根据配置转换值
if (config.type === 'number') {
initialData[key] = Number(value)
} else if (config.type === 'boolean') {
initialData[key] = value === 'true'
} else if (config.type === 'array') {
initialData[key] = value.split(',').filter(Boolean)
} else {
initialData[key] = value
}
} else if (config.default !== undefined) {
initialData[key] = config.default
}
})
setFormData(initialData)
}, [location.search, schema])
// 更新表单数据并同步到URL
const updateField = useCallback((key, value) => {
const newData = { ...formData, [key]: value }
setFormData(newData)
// 防抖更新URL
setTimeout(() => {
const searchParams = new URLSearchParams()
Object.entries(newData).forEach(([dataKey, dataValue]) => {
if (dataValue !== undefined && dataValue !== null && dataValue !== '') {
if (Array.isArray(dataValue)) {
searchParams.set(dataKey, dataValue.join(','))
} else {
searchParams.set(dataKey, String(dataValue))
}
}
})
const search = searchParams.toString()
navigate(search ? `${location.pathname}?${search}` : location.pathname, {
replace: true
})
}, 300)
}, [formData, navigate, location.pathname])
// 重置表单
const resetForm = useCallback(() => {
const defaults = {}
Object.entries(schema).forEach(([key, config]) => {
if (config.default !== undefined) {
defaults[key] = config.default
}
})
setFormData(defaults)
navigate(location.pathname, { replace: true })
}, [schema, navigate, location.pathname])
return {
formData,
updateField,
resetForm
}
}
// 使用示例 - 搜索过滤器
function SearchFilters() {
const formSchema = {
query: { type: 'string', default: '' },
category: { type: 'string', default: '' },
priceMin: { type: 'number', default: 0 },
priceMax: { type: 'number', default: 10000 },
inStock: { type: 'boolean', default: true },
tags: { type: 'array', default: [] }
}
const { formData, updateField, resetForm } = useFormSync(formSchema)
return (
<div className="search-filters">
<div className="filter-group">
<label>搜索关键词</label>
<input
type="text"
value={formData.query}
onChange={(e) => updateField('query', e.target.value)}
placeholder="输入搜索关键词..."
/>
</div>
<div className="filter-group">
<label>分类</label>
<select
value={formData.category}
onChange={(e) => updateField('category', e.target.value)}
>
<option value="">全部分类</option>
<option value="electronics">电子产品</option>
<option value="clothing">服装</option>
<option value="books">图书</option>
</select>
</div>
<div className="filter-group">
<label>价格范围</label>
<div className="price-range">
<input
type="number"
value={formData.priceMin}
onChange={(e) => updateField('priceMin', Number(e.target.value))}
min="0"
/>
<span>-</span>
<input
type="number"
value={formData.priceMax}
onChange={(e) => updateField('priceMax', Number(e.target.value))}
min="0"
/>
</div>
</div>
<div className="filter-group">
<label>
<input
type="checkbox"
checked={formData.inStock}
onChange={(e) => updateField('inStock', e.target.checked)}
/>
仅显示有货商品
</label>
</div>
<button onClick={resetForm} className="reset-button">
重置过滤器
</button>
</div>
)
}
这个章节详细讲解了动态路由和参数处理的各个方面,从基础的路径参数和查询参数,到高级的参数验证、转换和状态同步。通过这些技术,你可以构建功能强大、用户友好的单页应用路由系统。
6.4 嵌套路由和布局设计
6.4.1 嵌套路由基础概念
嵌套路由的原理:
// 嵌套路由允许在父路由中定义子路由
// 子路由的内容将在父组件的 <Outlet /> 位置渲染
import { Routes, Route, Outlet } from 'react-router-dom'
// 父路由布局组件
function UsersLayout() {
return (
<div className="users-layout">
<header>
<h1>用户管理</h1>
<nav>
<Link to="list">用户列表</Link>
<Link to="create">创建用户</Link>
<Link to="statistics">统计信息</Link>
</nav>
</header>
<main>
{/* 子路由内容将在这里渲染 */}
<Outlet />
</main>
</div>
)
}
// 嵌套路由配置
function App() {
return (
<Routes>
<Route path="/users" element={<UsersLayout />}>
{/* 索引路由 - 当访问 /users 时渲染 */}
<Route index element={<UserList />} />
{/* 子路由 */}
<Route path="list" element={<UserList />} />
<Route path="create" element={<CreateUser />} />
<Route path="statistics" element={<UserStatistics />} />
{/* 嵌套更深层的路由 */}
<Route path=":userId" element={<UserDetail />}>
<Route index element={<UserInfo />} />
<Route path="posts" element={<UserPosts />} />
<Route path="comments" element={<UserComments />} />
<Route path="settings" element={<UserSettings />} />
</Route>
</Route>
</Routes>
)
}
Outlet 组件详解:
// Outlet 组件用于渲染匹配的子路由元素
// 它就像一个占位符,子路由的内容会在这里显示
import { Outlet } from 'react-router-dom'
function LayoutWithSidebar() {
return (
<div className="app-layout">
<header className="app-header">
<h1>我的应用</h1>
<MainNavigation />
</header>
<div className="app-body">
<aside className="sidebar">
<SidebarNavigation />
</aside>
<main className="main-content">
{/* 这里会渲染匹配的子路由 */}
<Outlet />
</main>
</div>
<footer className="app-footer">
<p>© 2024 我的应用</p>
</footer>
</div>
)
}
// 获取当前子路由信息
function LayoutWithContext() {
const location = useLocation()
const outletContext = useOutletContext()
return (
<div className="layout">
<div className="layout-header">
<h2>当前路径: {location.pathname}</h2>
<p>上下文数据: {JSON.stringify(outletContext)}</p>
</div>
<div className="layout-content">
<Outlet context={{ layoutData: 'from-layout' }} />
</div>
</div>
)
}
相对路径 vs 绝对路径:
// 路径解析规则
function RouteExamples() {
return (
<Routes>
{/* 父路由 */}
<Route path="/admin" element={<AdminLayout />}>
{/* 绝对路径 - 以 / 开头 */}
<Route path="/dashboard" element={<Dashboard />} />
{/* 解析为: /dashboard */}
{/* 相对路径 - 不以 / 开头 */}
<Route path="users" element={<Users />} />
{/* 解析为: /admin/users */}
{/* 相对路径中的 . */}
<Route path="./overview" element={<Overview />} />
{/* 解析为: /admin/overview */}
{/* 相对路径中的 .. */}
<Route path="../settings" element={<Settings />} />
{/* 解析为: /settings */}
</Route>
</Routes>
)
}
// 编程式导航中的路径解析
function NavigationExample() {
const navigate = useNavigate()
const location = useLocation()
// 假设当前路径是 /admin/users
const handleNavigation = () => {
// 相对导航
navigate('create') // /admin/users/create
navigate('../settings') // /admin/settings
navigate('../../dashboard') // /dashboard
// 绝对导航
navigate('/login') // /login
}
return <button onClick={handleNavigation}>导航示例</button>
}
6.4.2 复杂嵌套路由架构
多层级嵌套示例:
// 完整的企业级应用路由架构
// 1. 根布局
function RootLayout() {
return (
<div className="root-layout">
<GlobalHeader />
<div className="root-content">
<Outlet />
</div>
<GlobalFooter />
</div>
)
}
// 2. 主应用布局(包含侧边栏)
function MainLayout() {
const location = useLocation()
return (
<div className="main-layout">
<aside className="sidebar">
<SidebarNavigation currentPath={location.pathname} />
</aside>
<main className="main-area">
<div className="breadcrumb-container">
<Breadcrumb />
</div>
<div className="page-content">
<Outlet />
</div>
</main>
</div>
)
}
// 3. 模块特定布局
function ProductLayout() {
const { productId } = useParams()
return (
<div className="product-layout">
<ProductHeader productId={productId} />
<nav className="product-tabs">
<NavLink to="overview">概览</NavLink>
<NavLink to="specifications">规格</NavLink>
<NavLink to="reviews">评价</NavLink>
<NavLink to="related">相关产品</NavLink>
</nav>
<div className="product-content">
<Outlet context={{ productId }} />
</div>
</div>
)
}
// 4. 完整的路由配置
function AppRoutes() {
return (
<Routes>
<Route path="/" element={<RootLayout />}>
{/* 公共页面 */}
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="help" element={<Help />}>
<Route index element={<HelpIndex />} />
<Route path="faq" element={<FAQ />} />
<Route path="tutorials" element={<Tutorials />} />
</Route>
{/* 认证相关 */}
<Route path="auth" element={<AuthLayout />}>
<Route path="login" element={<Login />} />
<Route path="register" element={<Register />} />
<Route path="forgot-password" element={<ForgotPassword />} />
<Route path="reset-password" element={<ResetPassword />} />
</Route>
{/* 主应用区域 */}
<Route path="app" element={<ProtectedRoute><MainLayout /></ProtectedRoute>}>
<Route index element={<Dashboard />} />
{/* 用户管理模块 */}
<Route path="users" element={<UserManagement />}>
<Route index element={<UserList />} />
<Route path="create" element={<CreateUser />} />
<Route path=":userId" element={<UserDetail />}>
<Route index element={<UserProfile />} />
<Route path="edit" element={<EditUser />} />
<Route path="permissions" element={<UserPermissions />} />
<Route path="activity" element={<UserActivity />} />
</Route>
</Route>
{/* 产品管理模块 */}
<Route path="products" element={<ProductManagement />}>
<Route index element={<ProductList />} />
<Route path="create" element={<CreateProduct />} />
<Route path=":productId" element={<ProductLayout />}>
<Route index element={<ProductOverview />} />
<Route path="specifications" element={<ProductSpecs />} />
<Route path="reviews" element={<ProductReviews />} />
<Route path="related" element={<RelatedProducts />} />
<Route path="edit" element={<EditProduct />} />
</Route>
</Route>
{/* 订单管理模块 */}
<Route path="orders" element={<OrderManagement />}>
<Route index element={<OrderList />} />
<Route path=":orderId" element={<OrderDetail />} />
<Route path=":orderId/edit" element={<EditOrder />} />
<Route path="statistics" element={<OrderStatistics />} />
</Route>
{/* 系统设置 */}
<Route path="settings" element={<SettingsLayout />}>
<Route index element={<GeneralSettings />} />
<Route path="security" element={<SecuritySettings />} />
<Route path="notifications" element={<NotificationSettings />} />
<Route path="integrations" element={<IntegrationSettings />} />
</Route>
</Route>
{/* 404 页面 */}
<Route path="*" element={<NotFound />} />
</Route>
</Routes>
)
}
动态嵌套路由生成:
// 基于配置的动态路由生成
const routeConfig = [
{
path: '/dashboard',
component: 'Dashboard',
layout: 'MainLayout',
permissions: ['dashboard.view'],
children: []
},
{
path: '/users',
component: 'UserManagement',
layout: 'MainLayout',
permissions: ['users.view'],
children: [
{
path: 'create',
component: 'CreateUser',
permissions: ['users.create']
},
{
path: ':userId',
component: 'UserDetail',
permissions: ['users.view'],
children: [
{
path: 'edit',
component: 'EditUser',
permissions: ['users.edit']
},
{
path: 'permissions',
component: 'UserPermissions',
permissions: ['users.manage']
}
]
}
]
}
]
// 动态路由生成器
function generateRoutes(config, layouts = {}, components = {}) {
return config.map((route) => {
const RouteComponent = components[route.component]
const LayoutComponent = layouts[route.layout]
if (!RouteComponent) {
console.warn(`Component ${route.component} not found`)
return null
}
const element = LayoutComponent ? (
<LayoutComponent>
<RouteComponent />
</LayoutComponent>
) : (
<RouteComponent />
)
const routeElement = (
<Route
key={route.path}
path={route.path}
element={element}
/>
)
// 递归处理子路由
if (route.children && route.children.length > 0) {
return (
<Route key={route.path} path={route.path} element={element}>
{generateRoutes(route.children, {}, components)}
</Route>
)
}
return routeElement
})
}
// 权限检查包装器
function withPermissionCheck(component, requiredPermissions) {
return function PermissionWrapper(props) {
const { hasPermission } = useAuth()
if (!hasPermission(requiredPermissions)) {
return <Unauthorized />
}
return React.createElement(component, props)
}
}
// 使用示例
function DynamicRoutes() {
const layouts = {
MainLayout,
RootLayout,
AuthLayout
}
const components = {
Dashboard,
UserManagement,
CreateUser,
UserDetail,
EditUser,
UserPermissions
}
const routes = generateRoutes(routeConfig, layouts, components)
return <Routes>{routes}</Routes>
}
6.4.3 布局组件设计模式
通用布局模式:
// 1. 有条件布局 - 根据路由选择不同布局
function ConditionalLayout() {
const location = useLocation()
// 判断当前路径是否需要特定布局
const getLayoutComponent = () => {
if (location.pathname.startsWith('/admin')) {
return AdminLayout
}
if (location.pathname.startsWith('/app')) {
return AppLayout
}
return PublicLayout
}
const Layout = getLayoutComponent()
return (
<Layout>
<Outlet />
</Layout>
)
}
// 2. 多布局组合模式
function CompositeLayout() {
const { user, isAuthenticated } = useAuth()
const location = useLocation()
return (
<>
{/* 全局布局 */}
<GlobalHeader />
<div className="main-wrapper">
{/* 条件侧边栏 */}
{isAuthenticated && !location.pathname.includes('/auth') && (
<Sidebar />
)}
{/* 主要内容区域 */}
<main className="main-content">
<Outlet />
</main>
</div>
{/* 条件页脚 */}
{!location.pathname.startsWith('/app') && (
<Footer />
)}
</>
)
}
// 3. 自适应布局
function AdaptiveLayout() {
const [isMobile, setIsMobile] = useState(window.innerWidth < 768)
const [sidebarOpen, setSidebarOpen] = useState(!isMobile)
useEffect(() => {
const handleResize = () => {
const mobile = window.innerWidth < 768
setIsMobile(mobile)
setSidebarOpen(!mobile)
}
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
return (
<div className={`adaptive-layout ${isMobile ? 'mobile' : 'desktop'}`}>
{/* 移动端顶部栏 */}
{isMobile && (
<header className="mobile-header">
<button
onClick={() => setSidebarOpen(!sidebarOpen)}
className="menu-toggle"
>
☰
</button>
<h1>应用</h1>
</header>
)}
<div className="layout-body">
{/* 侧边栏 */}
<aside className={`sidebar ${sidebarOpen ? 'open' : 'closed'}`}>
<Sidebar onClose={() => setSidebarOpen(false)} />
</aside>
{/* 主内容 */}
<main className="content">
<Outlet />
</main>
</div>
</div>
)
}
布局上下文传递:
// 创建布局上下文
const LayoutContext = createContext()
// 布局提供者组件
function LayoutProvider({ children }) {
const [layoutConfig, setLayoutConfig] = useState({
showSidebar: true,
sidebarCollapsed: false,
theme: 'light',
breadcrumbs: []
})
const updateLayout = useCallback((updates) => {
setLayoutConfig(prev => ({ ...prev, ...updates }))
}, [])
const value = {
layout: layoutConfig,
updateLayout,
toggleSidebar: () => {
setLayoutConfig(prev => ({
...prev,
sidebarCollapsed: !prev.sidebarCollapsed
}))
}
}
return (
<LayoutContext.Provider value={value}>
{children}
</LayoutContext.Provider>
)
}
// 使用布局上下文的 Hook
function useLayout() {
const context = useContext(LayoutContext)
if (!context) {
throw new Error('useLayout must be used within LayoutProvider')
}
return context
}
// 智能布局组件
function SmartLayout() {
const { layout, updateLayout } = useLayout()
const location = useLocation()
// 根据路由更新面包屑
useEffect(() => {
const breadcrumbs = generateBreadcrumbs(location.pathname)
updateLayout({ breadcrumbs })
}, [location.pathname, updateLayout])
// 根据路由决定是否显示侧边栏
useEffect(() => {
const hideSidebarRoutes = ['/login', '/register', '/404']
const shouldShowSidebar = !hideSidebarRoutes.some(route =>
location.pathname.startsWith(route)
)
if (layout.showSidebar !== shouldShowSidebar) {
updateLayout({ showSidebar: shouldShowSidebar })
}
}, [location.pathname, layout.showSidebar, updateLayout])
return (
<div className={`smart-layout ${layout.sidebarCollapsed ? 'sidebar-collapsed' : ''}`}>
{/* 动态显示的头部 */}
{(location.pathname !== '/login' && location.pathname !== '/register') && (
<Header breadcrumbs={layout.breadcrumbs} />
)}
{/* 动态显示的侧边栏 */}
{layout.showSidebar && (
<Sidebar collapsed={layout.sidebarCollapsed} />
)}
{/* 主内容区域 */}
<main className={layout.showSidebar ? 'with-sidebar' : 'full-width'}>
<Outlet />
</main>
</div>
)
}
// 面包屑生成器
function generateBreadcrumbs(pathname) {
const pathSegments = pathname.split('/').filter(Boolean)
const breadcrumbs = [{ label: '首页', path: '/' }]
let currentPath = ''
pathSegments.forEach((segment, index) => {
currentPath += `/${segment}`
// 根据路径段生成面包屑标签
const labelMap = {
'users': '用户管理',
'products': '产品管理',
'orders': '订单管理',
'settings': '系统设置',
'create': '创建',
'edit': '编辑',
'dashboard': '控制台'
}
const label = labelMap[segment] || segment
// 如果是数字(可能是ID),特殊处理
if (!isNaN(segment)) {
const entityName = pathSegments[index - 1]
breadcrumbs.push({
label: `${getEntityLabel(entityName)}详情`,
path: currentPath
})
} else {
breadcrumbs.push({
label,
path: index === pathSegments.length - 1 ? null : currentPath
})
}
})
return breadcrumbs
}
6.4.4 布局组件通信和状态管理
跨布局组件通信:
// 使用事件总线进行布局组件通信
class LayoutEventBus {
constructor() {
this.events = new Map()
}
on(event, callback) {
if (!this.events.has(event)) {
this.events.set(event, [])
}
this.events.get(event).push(callback)
}
off(event, callback) {
if (this.events.has(event)) {
const callbacks = this.events.get(event)
const index = callbacks.indexOf(callback)
if (index > -1) {
callbacks.splice(index, 1)
}
}
}
emit(event, data) {
if (this.events.has(event)) {
this.events.get(event).forEach(callback => callback(data))
}
}
}
const layoutEventBus = new LayoutEventBus()
// 侧边栏组件
function Sidebar() {
const [isCollapsed, setIsCollapsed] = useState(false)
useEffect(() => {
const handleCollapseToggle = (collapsed) => {
setIsCollapsed(collapsed)
}
layoutEventBus.on('sidebar:toggle', handleCollapseToggle)
return () => {
layoutEventBus.off('sidebar:toggle', handleCollapseToggle)
}
}, [])
return (
<aside className={`sidebar ${isCollapsed ? 'collapsed' : ''}`}>
{/* 侧边栏内容 */}
</aside>
)
}
// 头部组件
function Header() {
const handleMenuToggle = () => {
layoutEventBus.emit('sidebar:toggle', true)
}
return (
<header className="header">
<button onClick={handleMenuToggle}>菜单</button>
</header>
)
}
布局状态持久化:
// 布局状态管理 Hook
function useLayoutState() {
const [state, setState] = useState(() => {
// 从 localStorage 恢复状态
const saved = localStorage.getItem('layout-state')
return saved ? JSON.parse(saved) : {
sidebarCollapsed: false,
theme: 'light',
language: 'zh-CN',
compactMode: false
}
})
// 状态变化时持久化
useEffect(() => {
localStorage.setItem('layout-state', JSON.stringify(state))
}, [state])
const updateLayoutState = useCallback((updates) => {
setState(prev => ({ ...prev, ...updates }))
}, [])
const resetLayoutState = useCallback(() => {
const defaultState = {
sidebarCollapsed: false,
theme: 'light',
language: 'zh-CN',
compactMode: false
}
setState(defaultState)
localStorage.removeItem('layout-state')
}, [])
return {
layoutState: state,
updateLayoutState,
resetLayoutState
}
}
// 响应式布局管理
function useResponsiveLayout() {
const [layout, setLayout] = useLayoutState()
const [screenSize, setScreenSize] = useState({
width: window.innerWidth,
height: window.innerHeight
})
// 响应屏幕尺寸变化
useEffect(() => {
const handleResize = () => {
setScreenSize({
width: window.innerWidth,
height: window.innerHeight
})
}
window.addEventListener('resize', handleResize)
return () => window.removeEventListener('resize', handleResize)
}, [])
// 根据屏幕尺寸自动调整布局
useEffect(() => {
if (screenSize.width < 768) {
// 移动端 - 自动折叠侧边栏
setLayout(prev => ({ ...prev, sidebarCollapsed: true }))
} else if (screenSize.width < 1200) {
// 平板 - 可选折叠
// 保持用户选择
} else {
// 桌面端 - 默认展开
setLayout(prev => ({ ...prev, sidebarCollapsed: false }))
}
}, [screenSize.width, setLayout])
return {
layout,
screenSize,
isMobile: screenSize.width < 768,
isTablet: screenSize.width >= 768 && screenSize.width < 1200,
isDesktop: screenSize.width >= 1200
}
}
// 布局适配器组件
function LayoutAdapter({ children }) {
const { layout, updateLayoutState } = useLayoutState()
const { isMobile, isTablet, isDesktop } = useResponsiveLayout()
const LayoutComponent = useMemo(() => {
if (isMobile) return MobileLayout
if (isTablet) return TabletLayout
if (isDesktop) return DesktopLayout
return DefaultLayout
}, [isMobile, isTablet, isDesktop])
return (
<LayoutComponent
layout={layout}
updateLayout={updateLayoutState}
>
{children}
</LayoutComponent>
)
}
// 特定设备的布局组件
function MobileLayout({ children, layout, updateLayout }) {
return (
<div className="mobile-layout">
<MobileHeader onMenuToggle={() => updateLayout({ sidebarOpen: true })} />
{layout.sidebarOpen && (
<MobileOverlay onClick={() => updateLayout({ sidebarOpen: false })}>
<MobileSidebar onClose={() => updateLayout({ sidebarOpen: false })} />
</MobileOverlay>
)}
<main className="mobile-content">
{children}
</main>
<MobileNavigation />
</div>
)
}
function DesktopLayout({ children, layout, updateLayout }) {
return (
<div className="desktop-layout">
<DesktopHeader />
<div className="desktop-body">
<DesktopSidebar
collapsed={layout.sidebarCollapsed}
onToggle={() => updateLayout({
sidebarCollapsed: !layout.sidebarCollapsed
})}
/>
<main className={`desktop-content ${layout.sidebarCollapsed ? 'expanded' : ''}`}>
{children}
</main>
</div>
</div>
)
}
通过嵌套路由和精心设计的布局系统,你可以构建出结构清晰、用户体验良好的复杂单页应用。这种架构不仅提高了代码的可维护性,还为后续的功能扩展提供了良好的基础。
6.5 路由守卫和权限控制
6.5.1 基础路由守卫
认证守卫:
// 基础的认证守卫组件
import { Navigate, useLocation } from 'react-router-dom'
import { useAuth } from '../contexts/AuthContext'
function AuthGuard({ children }) {
const { user, isLoading } = useAuth()
const location = useLocation()
// 显示加载状态
if (isLoading) {
return <AuthLoadingSpinner />
}
// 未认证时重定向到登录页
if (!user) {
return (
<Navigate
to="/login"
state={{ from: location }}
replace
/>
)
}
// 已认证,渲染子组件
return children
}
// 使用示例
function ProtectedRoute({ children }) {
return (
<AuthGuard>
{children}
</AuthGuard>
)
}
// 在路由配置中使用
function AppRoutes() {
return (
<Routes>
{/* 公共路由 */}
<Route path="/login" element={<Login />} />
<Route path="/register" element={<Register />} />
{/* 受保护的路由 */}
<Route path="/dashboard" element={
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
} />
<Route path="/profile" element={
<ProtectedRoute>
<Profile />
</ProtectedRoute>
} />
</Routes>
)
}
权限守卫:
// 基于角色的权限守卫
function RoleGuard({ requiredRoles, children }) {
const { user, hasRole } = useAuth()
if (!user) {
return <Navigate to="/login" replace />
}
// 检查用户是否具有所需角色
const hasRequiredRole = requiredRoles.some(role => hasRole(role))
if (!hasRequiredRole) {
return <Unauthorized />
}
return children
}
// 权限检查守卫
function PermissionGuard({ requiredPermissions, children }) {
const { user, hasPermission } = useAuth()
if (!user) {
return <Navigate to="/login" replace />
}
// 检查用户是否具有所需权限
const hasRequiredPermission = requiredPermissions.some(permission =>
hasPermission(permission)
)
if (!hasRequiredPermission) {
return <Forbidden />
}
return children
}
// 组合守卫
function ProtectedRoute({
requireAuth = true,
requiredRoles = [],
requiredPermissions = [],
children
}) {
const { user, isLoading } = useAuth()
// 加载状态
if (isLoading) {
return <PageLoader />
}
// 认证检查
if (requireAuth && !user) {
return <Navigate to="/login" replace />
}
// 角色检查
if (requiredRoles.length > 0) {
return (
<RoleGuard requiredRoles={requiredRoles}>
<PermissionGuard requiredPermissions={requiredPermissions}>
{children}
</PermissionGuard>
</RoleGuard>
)
}
// 权限检查
if (requiredPermissions.length > 0) {
return (
<PermissionGuard requiredPermissions={requiredPermissions}>
{children}
</PermissionGuard>
)
}
return children
}
// 使用示例
function AdminRoutes() {
return (
<Routes>
<Route path="/admin" element={
<ProtectedRoute
requireAuth={true}
requiredRoles={['admin', 'super_admin']}
requiredPermissions={['admin.access']}
>
<AdminDashboard />
</ProtectedRoute>
} />
</Routes>
)
}
6.5.2 高级权限控制系统
权限数据结构:
// 权限系统数据结构
const permissionStructure = {
// 用户角色
roles: {
super_admin: {
name: '超级管理员',
level: 100,
permissions: ['*'] // 所有权限
},
admin: {
name: '管理员',
level: 80,
permissions: [
'users.view', 'users.create', 'users.edit', 'users.delete',
'products.view', 'products.create', 'products.edit',
'orders.view', 'orders.edit',
'analytics.view'
]
},
editor: {
name: '编辑员',
level: 50,
permissions: [
'products.view', 'products.create', 'products.edit',
'content.view', 'content.create', 'content.edit'
]
},
user: {
name: '普通用户',
level: 10,
permissions: [
'profile.view', 'profile.edit',
'orders.view',
'products.view'
]
},
guest: {
name: '访客',
level: 1,
permissions: [
'products.view',
'content.view'
]
}
},
// 权限分组
permissionGroups: {
user_management: ['users.view', 'users.create', 'users.edit', 'users.delete'],
product_management: ['products.view', 'products.create', 'products.edit', 'products.delete'],
order_management: ['orders.view', 'orders.create', 'orders.edit', 'orders.delete'],
content_management: ['content.view', 'content.create', 'content.edit', 'content.delete'],
analytics: ['analytics.view', 'analytics.export'],
system: ['system.config', 'system.logs']
}
}
// 权限检查 Hook
function usePermissions() {
const { user } = useAuth()
const hasPermission = useCallback((permission) => {
if (!user) return false
const userRole = permissionStructure.roles[user.role]
if (!userRole) return false
// 超级权限
if (userRole.permissions.includes('*')) return true
// 直接权限检查
if (userRole.permissions.includes(permission)) return true
// 通配符权限检查
const wildcardPermissions = userRole.permissions.filter(p => p.endsWith('.*'))
return wildcardPermissions.some(wildcard =>
permission.startsWith(wildcard.replace('.*', ''))
)
}, [user])
const hasRole = useCallback((role) => {
return user?.role === role
}, [user])
const hasAnyRole = useCallback((roles) => {
return roles.some(role => user?.role === role)
}, [user])
const hasAnyPermission = useCallback((permissions) => {
return permissions.some(permission => hasPermission(permission))
}, [hasPermission])
const hasAllPermissions = useCallback((permissions) => {
return permissions.every(permission => hasPermission(permission))
}, [hasPermission])
const getPermissionsByGroup = useCallback((group) => {
return permissionStructure.permissionGroups[group] || []
}, [])
return {
hasPermission,
hasRole,
hasAnyRole,
hasAnyPermission,
hasAllPermissions,
getPermissionsByGroup,
userRole: user?.role,
userPermissions: permissionStructure.roles[user?.role]?.permissions || []
}
}
动态权限路由生成:
// 基于权限的动态路由生成
function generateAuthorizedRoutes() {
const { hasPermission, hasRole } = usePermissions()
const allRoutes = [
{
path: '/dashboard',
component: Dashboard,
permissions: ['dashboard.view']
},
{
path: '/users',
component: UserManagement,
permissions: ['users.view'],
children: [
{
path: 'create',
component: CreateUser,
permissions: ['users.create']
},
{
path: ':userId/edit',
component: EditUser,
permissions: ['users.edit']
}
]
},
{
path: '/products',
component: ProductManagement,
permissions: ['products.view']
},
{
path: '/admin',
component: AdminPanel,
roles: ['admin', 'super_admin']
}
]
const filterRoutes = (routes) => {
return routes.filter(route => {
// 检查角色权限
if (route.roles && !hasAnyRole(route.roles)) {
return false
}
// 检查功能权限
if (route.permissions && !hasAnyPermission(route.permissions)) {
return false
}
// 递归过滤子路由
if (route.children) {
route.children = filterRoutes(route.children)
// 如果子路由全部被过滤掉,但父路由有权限,保留父路由
}
return true
})
}
return filterRoutes(allRoutes)
}
// 权限包装器组件
function AuthorizedRoute({
component: Component,
permissions = [],
roles = [],
requireAll = false,
fallback = <Forbidden />,
...props
}) {
const { hasAnyPermission, hasAllPermissions, hasAnyRole } = usePermissions()
// 角色检查
if (roles.length > 0) {
const hasRequiredRole = requireAll
? roles.every(role => hasRole(role))
: hasAnyRole(roles)
if (!hasRequiredRole) {
return fallback
}
}
// 权限检查
if (permissions.length > 0) {
const hasRequiredPermission = requireAll
? hasAllPermissions(permissions)
: hasAnyPermission(permissions)
if (!hasRequiredPermission) {
return fallback
}
}
return <Component {...props} />
}
// 路由权限装饰器
function withRouteProtection(WrappedComponent, options = {}) {
return function ProtectedComponent(props) {
const {
permissions = [],
roles = [],
requireAll = false,
fallback = <Forbidden />,
redirectTo = null
} = options
const { hasAnyPermission, hasAllPermissions, hasAnyRole } = usePermissions()
const navigate = useNavigate()
// 角色检查
if (roles.length > 0) {
const hasRequiredRole = requireAll
? roles.every(role => hasRole(role))
: hasAnyRole(roles)
if (!hasRequiredRole) {
if (redirectTo) {
navigate(redirectTo)
return null
}
return fallback
}
}
// 权限检查
if (permissions.length > 0) {
const hasRequiredPermission = requireAll
? hasAllPermissions(permissions)
: hasAnyPermission(permissions)
if (!hasRequiredPermission) {
if (redirectTo) {
navigate(redirectTo)
return null
}
return fallback
}
}
return <WrappedComponent {...props} />
}
}
// 使用装饰器
const ProtectedUserList = withRouteProtection(UserList, {
permissions: ['users.view'],
fallback: <div>您没有查看用户列表的权限</div>
})
6.5.3 路由级别的数据预加载
Loader 函数与权限:
// 带权限检查的数据加载器
function createAuthorizedLoader(loader, permissions = [], roles = []) {
return async ({ params, request }) => {
// 这里可以访问权限状态(需要从某个全局状态获取)
const user = getCurrentUser() // 假设有这个函数
// 权限检查
if (roles.length > 0 && !roles.includes(user?.role)) {
throw new Response('权限不足', { status: 403 })
}
if (permissions.length > 0 && !hasAnyPermission(permissions)) {
throw new Response('权限不足', { status: 403 })
}
try {
return await loader({ params, request })
} catch (error) {
throw new Response('数据加载失败', { status: 500 })
}
}
}
// 用户详情加载器
const userDetailLoader = createAuthorizedLoader(
async ({ params }) => {
const response = await fetch(`/api/users/${params.userId}`)
if (!response.ok) {
throw new Response('用户未找到', { status: 404 })
}
return response.json()
},
['users.view']
)
// 带权限的数据预加载路由配置
function AuthorizedRoutes() {
return (
<Routes>
<Route
path="/users/:userId"
element={<UserDetail />}
loader={userDetailLoader}
errorElement={<ErrorBoundary />}
/>
<Route
path="/products/:productId/edit"
element={<EditProduct />}
loader={createAuthorizedLoader(
async ({ params }) => {
const response = await fetch(`/api/products/${params.productId}`)
if (!response.ok) {
throw new Response('产品未找到', { status: 404 })
}
const product = await response.json()
return { product }
},
['products.edit']
)}
/>
</Routes>
)
}
权限感知的错误边界:
// 权限相关的错误边界
class PermissionErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false, error: null }
}
static getDerivedStateFromError(error) {
return { hasError: true, error }
}
componentDidCatch(error, errorInfo) {
console.error('权限错误边界捕获错误:', error, errorInfo)
// 如果是权限相关的错误,可以记录到日志系统
if (error.message.includes('权限') || error.status === 403) {
logPermissionError(error, errorInfo)
}
}
render() {
if (this.state.hasError) {
const isPermissionError =
this.state.error?.message?.includes('权限') ||
this.state.error?.status === 403
if (isPermissionError) {
return <PermissionErrorComponent error={this.state.error} />
}
return <GenericErrorComponent error={this.state.error} />
}
return this.props.children
}
}
// 权限错误组件
function PermissionErrorComponent({ error }) {
const navigate = useNavigate()
const { user } = useAuth()
const handleGoBack = () => {
navigate(-1)
}
const handleGoHome = () => {
navigate('/')
}
const handleContactAdmin = () => {
navigate('/contact-admin')
}
return (
<div className="permission-error">
<div className="error-icon">🚫</div>
<h1>访问被拒绝</h1>
<p>
抱歉,您没有权限访问此页面。
{user && <span> 当前角色: {user.role}</span>}
</p>
<div className="error-actions">
<button onClick={handleGoBack} className="btn-secondary">
返回上一页
</button>
<button onClick={handleGoHome} className="btn-primary">
回到首页
</button>
<button onClick={handleContactAdmin} className="btn-outline">
联系管理员
</button>
</div>
{error && (
<details className="error-details">
<summary>错误详情</summary>
<pre>{error.message}</pre>
</details>
)}
</div>
)
}
6.5.4 动态权限管理
实时权限更新:
// 权限状态管理
function PermissionProvider({ children }) {
const [permissions, setPermissions] = useState([])
const [roles, setRoles] = useState([])
const [isLoading, setIsLoading] = useState(true)
// 从服务器获取最新的权限信息
const refreshPermissions = useCallback(async () => {
try {
const response = await fetch('/api/auth/permissions')
const data = await response.json()
setPermissions(data.permissions)
setRoles(data.roles)
} catch (error) {
console.error('获取权限失败:', error)
} finally {
setIsLoading(false)
}
}, [])
// 初始化加载权限
useEffect(() => {
refreshPermissions()
}, [refreshPermissions])
// 定期刷新权限(可选)
useEffect(() => {
const interval = setInterval(refreshPermissions, 5 * 60 * 1000) // 5分钟
return () => clearInterval(interval)
}, [refreshPermissions])
// 权限变更监听
const handlePermissionChange = useCallback((newPermissions) => {
setPermissions(newPermissions)
}, [])
const value = {
permissions,
roles,
isLoading,
refreshPermissions,
updatePermissions: handlePermissionChange
}
return (
<PermissionContext.Provider value={value}>
{children}
</PermissionContext.Provider>
)
}
// 动态权限检查 Hook
function useDynamicPermissions() {
const { permissions, roles } = usePermissionContext()
const [dynamicPermissions, setDynamicPermissions] = useState({})
// 检查特定资源权限
const checkResourcePermission = useCallback(async (resource, action) => {
// 缓存检查
const cacheKey = `${resource}:${action}`
if (dynamicPermissions[cacheKey] !== undefined) {
return dynamicPermissions[cacheKey]
}
try {
const response = await fetch(`/api/permissions/check`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ resource, action })
})
const result = await response.json()
// 更新缓存
setDynamicPermissions(prev => ({
...prev,
[cacheKey]: result.allowed
}))
return result.allowed
} catch (error) {
console.error('权限检查失败:', error)
return false
}
}, [])
// 清除权限缓存
const clearPermissionCache = useCallback(() => {
setDynamicPermissions({})
}, [])
return {
checkResourcePermission,
clearPermissionCache,
permissions,
roles
}
}
// 权限感知的路由组件
function DynamicPermissionRoute({
resource,
action,
children,
fallback = <Forbidden />
}) {
const { checkResourcePermission } = useDynamicPermissions()
const [hasPermission, setHasPermission] = useState(null)
const [isLoading, setIsLoading] = useState(true)
useEffect(() => {
let isMounted = true
const checkPermission = async () => {
setIsLoading(true)
try {
const allowed = await checkResourcePermission(resource, action)
if (isMounted) {
setHasPermission(allowed)
}
} catch (error) {
if (isMounted) {
setHasPermission(false)
}
} finally {
if (isMounted) {
setIsLoading(false)
}
}
}
checkPermission()
return () => {
isMounted = false
}
}, [resource, action, checkResourcePermission])
if (isLoading) {
return <PermissionLoadingSpinner />
}
if (hasPermission) {
return children
}
return fallback
}
// 使用示例
function ResourceManagement() {
return (
<Routes>
<Route
path="/documents/:docId/edit"
element={
<DynamicPermissionRoute
resource="document"
action="edit"
fallback={<div>您没有编辑此文档的权限</div>}
>
<EditDocument />
</DynamicPermissionRoute>
}
/>
<Route
path="/projects/:projectId/delete"
element={
<DynamicPermissionRoute
resource="project"
action="delete"
fallback={<div>您没有删除此项目的权限</div>}
>
<DeleteProject />
</DynamicPermissionRoute>
}
/>
</Routes>
)
}
权限审计和日志:
// 权限审计系统
class PermissionAuditor {
constructor() {
this.logs = []
this.maxLogs = 1000
}
// 记录权限检查
logPermissionCheck({
user,
resource,
action,
granted,
ip,
userAgent,
timestamp = new Date()
}) {
const logEntry = {
id: this.generateLogId(),
user: user.id,
username: user.username,
role: user.role,
resource,
action,
granted,
ip,
userAgent,
timestamp: timestamp.toISOString(),
sessionId: this.getSessionId()
}
this.logs.unshift(logEntry)
// 保持日志数量在限制内
if (this.logs.length > this.maxLogs) {
this.logs = this.logs.slice(0, this.maxLogs)
}
// 发送到服务器
this.sendToServer(logEntry)
return logEntry
}
// 获取权限检查统计
getPermissionStats(userId, timeRange = '24h') {
const now = new Date()
const startTime = new Date(now.getTime() - this.parseTimeRange(timeRange))
const userLogs = this.logs.filter(log =>
log.user === userId &&
new Date(log.timestamp) >= startTime
)
const stats = {
totalChecks: userLogs.length,
grantedChecks: userLogs.filter(log => log.granted).length,
deniedChecks: userLogs.filter(log => !log.granted).length,
mostAccessedResources: this.getMostAccessedResources(userLogs),
deniedActions: this.getDeniedActions(userLogs)
}
return stats
}
generateLogId() {
return Date.now().toString(36) + Math.random().toString(36).substr(2)
}
parseTimeRange(range) {
const units = {
'h': 60 * 60 * 1000,
'd': 24 * 60 * 60 * 1000,
'w': 7 * 24 * 60 * 60 * 1000
}
const value = parseInt(range)
const unit = range.replace(value.toString(), '')
return value * (units[unit] || units['h'])
}
getMostAccessedResources(logs) {
const resourceCount = {}
logs.forEach(log => {
resourceCount[log.resource] = (resourceCount[log.resource] || 0) + 1
})
return Object.entries(resourceCount)
.sort(([,a], [,b]) => b - a)
.slice(0, 10)
}
getDeniedActions(logs) {
const deniedActions = logs
.filter(log => !log.granted)
.reduce((acc, log) => {
const key = `${log.resource}:${log.action}`
acc[key] = (acc[key] || 0) + 1
return acc
}, {})
return Object.entries(deniedActions)
.sort(([,a], [,b]) => b - a)
.slice(0, 10)
}
async sendToServer(logEntry) {
try {
await fetch('/api/audit/permissions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(logEntry)
})
} catch (error) {
console.error('发送审计日志失败:', error)
}
}
getSessionId() {
return sessionStorage.getItem('sessionId') || 'anonymous'
}
}
// 权限审计 Hook
function usePermissionAudit() {
const auditor = useMemo(() => new PermissionAuditor(), [])
const { user } = useAuth()
const auditPermissionCheck = useCallback((resource, action, granted) => {
auditor.logPermissionCheck({
user,
resource,
action,
granted,
ip: getClientIP(),
userAgent: navigator.userAgent
})
}, [auditor, user])
return { auditPermissionCheck }
}
// 带审计的权限检查 Hook
function useAuditedPermissions() {
const { hasPermission, hasRole, hasAnyPermission, hasAllPermissions } = usePermissions()
const { auditPermissionCheck } = usePermissionAudit()
const auditedHasPermission = useCallback((permission) => {
const granted = hasPermission(permission)
auditPermissionCheck('permission', permission, granted)
return granted
}, [hasPermission, auditPermissionCheck])
const auditedHasRole = useCallback((role) => {
const granted = hasRole(role)
auditPermissionCheck('role', role, granted)
return granted
}, [hasRole, auditPermissionCheck])
return {
hasPermission: auditedHasPermission,
hasRole: auditedHasRole,
hasAnyPermission,
hasAllPermissions
}
}
// 审计报告组件
function PermissionAuditReport({ userId }) {
const auditor = useMemo(() => new PermissionAuditor(), [])
const [stats, setStats] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
const loadStats = async () => {
try {
const userStats = auditor.getPermissionStats(userId)
setStats(userStats)
} catch (error) {
console.error('获取审计统计失败:', error)
} finally {
setLoading(false)
}
}
loadStats()
}, [auditor, userId])
if (loading) return <div>加载审计报告...</div>
return (
<div className="audit-report">
<h2>权限审计报告</h2>
<div className="stats-grid">
<div className="stat-card">
<h3>总检查次数</h3>
<p>{stats.totalChecks}</p>
</div>
<div className="stat-card">
<h3>授权次数</h3>
<p>{stats.grantedChecks}</p>
</div>
<div className="stat-card">
<h3>拒绝次数</h3>
<p>{stats.deniedChecks}</p>
</div>
<div className="stat-card">
<h3>授权率</h3>
<p>{((stats.grantedChecks / stats.totalChecks) * 100).toFixed(2)}%</p>
</div>
</div>
<div className="accessed-resources">
<h3>最常访问的资源</h3>
<ul>
{stats.mostAccessedResources.map(([resource, count]) => (
<li key={resource}>{resource}: {count} 次</li>
))}
</ul>
</div>
<div className="denied-actions">
<h3>被拒绝的操作</h3>
<ul>
{stats.deniedActions.map(([action, count]) => (
<li key={action}>{action}: {count} 次</li>
))}
</ul>
</div>
</div>
)
}
通过完善的路由守卫和权限控制系统,你可以确保应用的安全性和合规性,同时提供良好的用户体验。这种设计不仅保护了敏感资源,还为权限管理和审计提供了强大的工具。
6.6 路由状态管理和数据获取
6.6.1 React Router 的数据加载机制
Loader 函数详解:
// React Router v6.4+ 引入的数据加载器
// Loader 在路由匹配时自动执行,在组件渲染前准备好数据
// 基础 loader 示例
const userLoader = async ({ params }) => {
const response = await fetch(`/api/users/${params.userId}`)
if (!response.ok) {
throw new Response('用户未找到', { status: 404 })
}
return response.json()
}
// 复杂的 loader 示例
const productDetailLoader = async ({ params, request }) => {
// 获取查询参数
const url = new URL(request.url)
const tab = url.searchParams.get('tab') || 'overview'
try {
// 并行获取多个数据源
const [product, reviews, relatedProducts] = await Promise.all([
fetch(`/api/products/${params.productId}`).then(res => {
if (!res.ok) throw new Error('产品加载失败')
return res.json()
}),
fetch(`/api/products/${params.productId}/reviews`).then(res => res.json()),
fetch(`/api/products/${params.productId}/related`).then(res => res.json())
])
return {
product,
reviews,
relatedProducts,
activeTab: tab
}
} catch (error) {
throw new Response('数据加载失败', { status: 500 })
}
}
// 在路由配置中使用 loader
function AppRoutes() {
return (
<Routes>
<Route
path="/users/:userId"
element={<UserDetail />}
loader={userLoader}
errorElement={<ErrorBoundary />}
/>
<Route
path="/products/:productId"
element={<ProductDetail />}
loader={productDetailLoader}
errorElement={<ProductErrorBoundary />}
/>
</Routes>
)
}
使用 loader 数据:
// 在组件中使用 loader 加载的数据
import { useLoaderData } from 'react-router-dom'
function UserDetail() {
const user = useLoaderData() // 获取 loader 返回的数据
return (
<div className="user-detail">
<h1>{user.name}</h1>
<p>邮箱: {user.email}</p>
<p>角色: {user.role}</p>
<div className="user-actions">
<Link to="edit">编辑用户</Link>
<Link to="permissions">权限设置</Link>
</div>
</div>
)
}
// 复杂数据的使用
function ProductDetail() {
const { product, reviews, relatedProducts, activeTab } = useLoaderData()
return (
<div className="product-detail">
<ProductHeader product={product} />
<ProductTabNavigation activeTab={activeTab} productId={product.id} />
<div className="tab-content">
{activeTab === 'overview' && <ProductOverview product={product} />}
{activeTab === 'reviews' && <ProductReviews reviews={reviews} />}
{activeTab === 'related' && <RelatedProducts products={relatedProducts} />}
</div>
</div>
)
}
// 使用 defer 进行渐进式数据加载
import { defer } from 'react-router-dom'
const deferredLoader = async ({ params }) => {
// 立即返回的数据
const productPromise = fetch(`/api/products/${params.productId}`).then(res => res.json())
// 延迟加载的数据
const reviewsPromise = fetch(`/api/products/${params.productId}/reviews`).then(res => res.json())
const relatedPromise = fetch(`/api/products/${params.productId}/related`).then(res => res.json())
return defer({
product: productPromise,
reviews: reviewsPromise,
relatedProducts: relatedPromise
})
}
// 使用 deferred 数据
function DeferredProductDetail() {
const data = useLoaderData()
return (
<div className="product-detail">
{/* 立即显示的内容 */}
<React.Suspense fallback={<ProductSkeleton />}>
<Await resolve={data.product}>
{(product) => <ProductHeader product={product} />}
</Await>
</React.Suspense>
{/* 延迟显示的内容 */}
<React.Suspense fallback={<ReviewsSkeleton />}>
<Await resolve={data.reviews}>
{(reviews) => <ProductReviews reviews={reviews} />}
</Await>
</React.Suspense>
<React.Suspense fallback={<RelatedSkeleton />}>
<Await resolve={data.relatedProducts}>
{(related) => <RelatedProducts products={related} />}
</Await>
</React.Suspense>
</div>
)
}
6.6.2 路由状态管理
路由状态持久化:
// 路由状态管理 Hook
function useRouteState(key, defaultValue) {
const [state, setState] = useState(defaultValue)
const location = useLocation()
const navigate = useNavigate()
// 从 URL state 恢复状态
useEffect(() => {
if (location.state?.[key] !== undefined) {
setState(location.state[key])
}
}, [location.state, key])
// 更新状态并同步到 URL
const updateState = useCallback((newState) => {
setState(newState)
// 更新 URL state
const currentState = location.state || {}
navigate(location.pathname, {
state: { ...currentState, [key]: newState },
replace: true
})
}, [key, location, navigate])
// 重置状态
const resetState = useCallback(() => {
setState(defaultValue)
const currentState = location.state || {}
const newState = { ...currentState }
delete newState[key]
navigate(location.pathname, {
state: newState,
replace: true
})
}, [key, defaultValue, location, navigate])
return [state, updateState, resetState]
}
// 使用示例 - 表单状态管理
function ProductFilterForm() {
const [filters, setFilters, resetFilters] = useRouteState('productFilters', {
category: '',
priceRange: [0, 1000],
inStock: true,
sortBy: 'name'
})
const handleFilterChange = (field, value) => {
setFilters({ ...filters, [field]: value })
}
const handleReset = () => {
resetFilters()
}
return (
<form className="filter-form">
<div className="filter-group">
<label>分类</label>
<select
value={filters.category}
onChange={(e) => handleFilterChange('category', e.target.value)}
>
<option value="">全部分类</option>
<option value="electronics">电子产品</option>
<option value="clothing">服装</option>
</select>
</div>
<div className="filter-group">
<label>价格范围</label>
<RangeSlider
value={filters.priceRange}
onChange={(value) => handleFilterChange('priceRange', value)}
min={0}
max={2000}
/>
</div>
<div className="filter-group">
<label>
<input
type="checkbox"
checked={filters.inStock}
onChange={(e) => handleFilterChange('inStock', e.target.checked)}
/>
仅显示有货商品
</label>
</div>
<button type="button" onClick={handleReset}>
重置过滤器
</button>
</form>
)
}
// 复杂的状态管理 - 多个状态同步
function useAdvancedRouteState() {
const location = useLocation()
const navigate = useNavigate()
// 获取所有 URL 状态
const getRouteState = useCallback(() => {
return location.state || {}
}, [location.state])
// 更新多个状态
const updateRouteState = useCallback((updates) => {
const currentState = getRouteState()
const newState = { ...currentState, ...updates }
navigate(location.pathname + location.search, {
state: newState,
replace: true
})
}, [location, navigate, getRouteState])
// 清除特定状态
const clearRouteState = useCallback((keys) => {
const currentState = getRouteState()
const newState = { ...currentState }
(Array.isArray(keys) ? keys : [keys]).forEach(key => {
delete newState[key]
})
navigate(location.pathname + location.search, {
state: newState,
replace: true
})
}, [location, navigate, getRouteState])
// 清除所有状态
const clearAllRouteState = useCallback(() => {
navigate(location.pathname + location.search, {
state: {},
replace: true
})
}, [location, navigate])
return {
routeState: getRouteState(),
updateRouteState,
clearRouteState,
clearAllRouteState
}
}
// 使用示例 - 复杂的应用状态
function AdvancedProductList() {
const { routeState, updateRouteState, clearRouteState } = useAdvancedRouteState()
const handleFiltersChange = (filters) => {
updateRouteState({
filters,
lastUpdated: Date.now()
})
}
const handleViewModeChange = (viewMode) => {
updateRouteState({ viewMode })
}
const handleSelectionChange = (selectedIds) => {
updateRouteState({ selectedIds })
}
const clearSelection = () => {
clearRouteState(['selectedIds'])
}
return (
<div className="advanced-product-list">
<ProductFilters
filters={routeState.filters}
onChange={handleFiltersChange}
/>
<ViewControls
viewMode={routeState.viewMode}
onViewModeChange={handleViewModeChange}
selectedCount={routeState.selectedIds?.length || 0}
onClearSelection={clearSelection}
/>
<ProductGrid
filters={routeState.filters}
viewMode={routeState.viewMode}
selectedIds={routeState.selectedIds || []}
onSelectionChange={handleSelectionChange}
/>
</div>
)
}
6.6.3 路由级别的数据缓存
智能缓存系统:
// 路由数据缓存管理器
class RouteDataCache {
constructor() {
this.cache = new Map()
this.maxAge = 5 * 60 * 1000 // 5分钟
this.maxSize = 100 // 最大缓存条目数
}
// 生成缓存键
generateKey(path, params, search) {
const paramsStr = Object.entries(params || {}).sort().map(([k, v]) => `${k}:${v}`).join(',')
return `${path}${paramsStr ? `?${paramsStr}` : ''}${search}`
}
// 设置缓存
set(key, data, customMaxAge) {
const item = {
data,
timestamp: Date.now(),
maxAge: customMaxAge || this.maxAge
}
this.cache.set(key, item)
// 清理过期缓存
this.cleanup()
// 如果缓存过大,删除最旧的条目
if (this.cache.size > this.maxSize) {
const oldestKey = this.cache.keys().next().value
this.cache.delete(oldestKey)
}
}
// 获取缓存
get(key) {
const item = this.cache.get(key)
if (!item) return null
// 检查是否过期
if (Date.now() - item.timestamp > item.maxAge) {
this.cache.delete(key)
return null
}
return item.data
}
// 检查缓存是否存在且有效
has(key) {
return this.get(key) !== null
}
// 清理过期缓存
cleanup() {
const now = Date.now()
for (const [key, item] of this.cache.entries()) {
if (now - item.timestamp > item.maxAge) {
this.cache.delete(key)
}
}
}
// 清除特定缓存
clear(key) {
if (key) {
this.cache.delete(key)
} else {
this.cache.clear()
}
}
// 获取缓存统计
getStats() {
return {
size: this.cache.size,
maxSize: this.maxSize,
items: Array.from(this.cache.entries()).map(([key, item]) => ({
key,
age: Date.now() - item.timestamp,
maxAge: item.maxAge
}))
}
}
}
// 全局缓存实例
const routeDataCache = new RouteDataCache()
// 带缓存的 loader 创建器
function createCachedLoader(loader, options = {}) {
const {
cacheKey,
maxAge,
revalidateOnFocus = false,
revalidateOnReconnect = true
} = options
return async ({ params, request, signal }) => {
const url = new URL(request.url)
const key = cacheKey || routeDataCache.generateKey(url.pathname, params, url.search)
// 检查缓存
const cachedData = routeDataCache.get(key)
if (cachedData && !signal.aborted) {
return cachedData
}
try {
// 从服务器获取数据
const data = await loader({ params, request, signal })
// 缓存数据
routeDataCache.set(key, data, maxAge)
return data
} catch (error) {
// 如果有缓存数据,即使网络请求失败也返回缓存
if (cachedData) {
console.warn('网络请求失败,使用缓存数据:', error)
return cachedData
}
throw error
}
}
}
// 使用示例
const cachedProductLoader = createCachedLoader(
async ({ params }) => {
const response = await fetch(`/api/products/${params.productId}`)
if (!response.ok) throw new Error('产品加载失败')
return response.json()
},
{
cacheKey: 'product', // 自定义缓存键
maxAge: 10 * 60 * 1000 // 10分钟缓存
}
)
// 智能重新验证
function useCacheRevalidation() {
useEffect(() => {
// 窗口获得焦点时重新验证
const handleFocus = () => {
routeDataCache.cleanup()
}
// 网络重新连接时重新验证
const handleOnline = () => {
routeDataCache.clear() // 清除所有缓存,强制重新获取
}
window.addEventListener('focus', handleFocus)
window.addEventListener('online', handleOnline)
return () => {
window.removeEventListener('focus', handleFocus)
window.removeEventListener('online', handleOnline)
}
}, [])
}
6.6.4 数据预加载和后台更新
数据预加载策略:
// 预加载管理器
class PreloadManager {
constructor() {
this.preloadedData = new Map()
this.preloadingPromises = new Map()
}
// 预加载数据
async preload(key, loaderFunction) {
// 如果正在预加载,返回现有的 Promise
if (this.preloadingPromises.has(key)) {
return this.preloadingPromises.get(key)
}
// 如果已经预加载完成,直接返回
if (this.preloadedData.has(key)) {
return this.preloadedData.get(key)
}
// 开始预加载
const preloadPromise = this.doPreload(key, loaderFunction)
this.preloadingPromises.set(key, preloadPromise)
try {
const data = await preloadPromise
this.preloadedData.set(key, data)
return data
} finally {
this.preloadingPromises.delete(key)
}
}
async doPreload(key, loaderFunction) {
try {
return await loaderFunction()
} catch (error) {
console.warn(`预加载失败 ${key}:`, error)
throw error
}
}
// 获取预加载的数据
getPreloaded(key) {
return this.preloadedData.get(key)
}
// 清除预加载的数据
clear(key) {
if (key) {
this.preloadedData.delete(key)
this.preloadingPromises.delete(key)
} else {
this.preloadedData.clear()
this.preloadingPromises.clear()
}
}
}
// 全局预加载管理器
const preloadManager = new PreloadManager()
// 智能预加载 Hook
function useSmartPreload() {
const location = useLocation()
const navigate = useNavigate()
// 预加载相邻页面的数据
const preloadAdjacentRoutes = useCallback(async () => {
// 根据当前路径预测用户可能访问的下一页
const possibleNextRoutes = predictNextRoutes(location.pathname)
for (const route of possibleNextRoutes) {
const preloadKey = `route-${route.path}`
if (!preloadManager.getPreloaded(preloadKey)) {
try {
await preloadManager.preload(preloadKey, async () => {
return route.loader ? await route.loader() : null
})
} catch (error) {
// 预加载失败不影响正常功能
console.warn('预加载失败:', error)
}
}
}
}, [location.pathname])
// 预加载特定路由
const preloadRoute = useCallback((path, loader) => {
const preloadKey = `route-${path}`
return preloadManager.preload(preloadKey, loader)
}, [])
// 获取预加载的数据
const getPreloadedData = useCallback((path) => {
const preloadKey = `route-${path}`
return preloadManager.getPreloaded(preloadKey)
}, [])
return {
preloadAdjacentRoutes,
preloadRoute,
getPreloadedData
}
}
// 路由预测算法
function predictNextRoutes(currentPath) {
const predictions = []
// 基于路径模式预测
if (currentPath.includes('/users/')) {
predictions.push(
{ path: '/users', loader: userLoader },
{ path: '/users/create', loader: createUserLoader }
)
}
if (currentPath.includes('/products/')) {
const productId = currentPath.split('/').pop()
predictions.push(
{ path: `/products/${productId}/edit`, loader: editProductLoader },
{ path: `/products/${productId}/reviews`, loader: productReviewsLoader }
)
}
// 基于用户行为预测(这里简化处理)
const recentRoutes = getRecentRoutes()
predictions.push(...recentRoutes.map(route => ({
path: route.path,
loader: getLoaderForPath(route.path)
})))
return predictions.slice(0, 3) // 最多预加载3个路由
}
// 后台数据更新
function useBackgroundDataUpdate() {
const location = useLocation()
useEffect(() => {
// 定期更新当前页面的数据
const interval = setInterval(async () => {
try {
// 获取当前路由的 loader
const currentLoader = getCurrentLoader(location.pathname)
if (currentLoader) {
const newData = await currentLoader()
// 更新缓存但不重新渲染
const cacheKey = routeDataCache.generateKey(
location.pathname,
location.state?.params,
location.search
)
routeDataCache.set(cacheKey, newData, 1000) // 短暂缓存
}
} catch (error) {
console.warn('后台数据更新失败:', error)
}
}, 30000) // 每30秒更新一次
return () => clearInterval(interval)
}, [location.pathname, location.state?.params, location.search])
}
// 使用示例
function SmartProductList() {
const { preloadAdjacentRoutes, preloadRoute } = useSmartPreload()
useEffect(() => {
// 预加载相邻路由
preloadAdjacentRoutes()
}, [preloadAdjacentRoutes])
const handleProductHover = useCallback((productId) => {
// 鼠标悬停时预加载产品详情
preloadRoute(`/products/${productId}`, async () => {
const response = await fetch(`/api/products/${productId}`)
return response.json()
})
}, [preloadRoute])
return (
<div className="product-list">
{products.map(product => (
<ProductCard
key={product.id}
product={product}
onMouseEnter={() => handleProductHover(product.id)}
/>
))}
</div>
)
}
// 渐进式数据加载
function ProgressiveDataLoader({ children }) {
const [criticalData, setCriticalData] = useState(null)
const [secondaryData, setSecondaryData] = useState(null)
useEffect(() => {
// 第一阶段:加载关键数据
const loadCriticalData = async () => {
const data = await loadCriticalPageData()
setCriticalData(data)
}
// 第二阶段:加载次要数据
const loadSecondaryData = async () => {
const data = await loadSecondaryPageData()
setSecondaryData(data)
}
loadCriticalData()
// 使用 requestIdleCallback 在空闲时加载次要数据
if ('requestIdleCallback' in window) {
requestIdleCallback(loadSecondaryData)
} else {
setTimeout(loadSecondaryData, 100)
}
}, [])
return children({ criticalData, secondaryData })
}
// 使用示例
function DataAwareProductList() {
return (
<ProgressiveDataLoader>
{({ criticalData, secondaryData }) => (
<div>
{/* 关键内容优先显示 */}
{criticalData ? (
<ProductGrid products={criticalData.products} />
) : (
<ProductGridSkeleton />
)}
{/* 次要内容延迟显示 */}
{secondaryData && (
<div className="secondary-content">
<RecommendationSection data={secondaryData.recommendations} />
<TrendingSection data={secondaryData.trending} />
</div>
)}
</div>
)}
</ProgressiveDataLoader>
)
}
通过完善的路由状态管理和数据获取系统,你可以构建高性能、用户体验良好的应用。这种设计确保了数据的一致性、缓存的有效性,以及智能的预加载策略,为用户提供流畅的浏览体验。
6.7 路由性能优化和懒加载
6.7.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 Products = lazy(() => import('./pages/Products'))
const ProductDetail = lazy(() => import('./pages/ProductDetail'))
const Contact = lazy(() => import('./pages/Contact'))
const NotFound = lazy(() => import('./pages/NotFound'))
// 加载组件
const PageLoader = () => (
<div className="page-loader">
<div className="loader-spinner" />
<p>页面加载中...</p>
</div>
)
// 错误边界组件
class LazyLoadErrorBoundary extends React.Component {
constructor(props) {
super(props)
this.state = { hasError: false, error: null }
}
static getDerivedStateFromError(error) {
return { hasError: true, error }
}
componentDidCatch(error, errorInfo) {
console.error('懒加载错误:', error, errorInfo)
}
render() {
if (this.state.hasError) {
return (
<div className="lazy-load-error">
<h2>页面加载失败</h2>
<p>抱歉,页面加载出现了问题</p>
<button onClick={() => window.location.reload()}>
重新加载
</button>
</div>
)
}
return this.props.children
}
}
// 路由配置
function AppRoutes() {
return (
<LazyLoadErrorBoundary>
<Suspense fallback={<PageLoader />}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/products" element={<Products />} />
<Route path="/products/:id" element={<ProductDetail />} />
<Route path="/contact" element={<Contact />} />
<Route path="*" element={<NotFound />} />
</Routes>
</Suspense>
</LazyLoadErrorBoundary>
)
}
高级懒加载策略:
// 预加载管理器
class PreloadManager {
constructor() {
this.preloadedModules = new Map()
this.preloadingPromises = new Map()
}
// 预加载模块
async preload(moduleName) {
// 如果已经预加载,直接返回
if (this.preloadedModules.has(moduleName)) {
return this.preloadedModules.get(moduleName)
}
// 如果正在预加载,返回现有的 Promise
if (this.preloadingPromises.has(moduleName)) {
return this.preloadingPromises.get(moduleName)
}
// 开始预加载
const preloadPromise = this.doPreload(moduleName)
this.preloadingPromises.set(moduleName, preloadPromise)
try {
const module = await preloadPromise
this.preloadedModules.set(moduleName, module)
return module
} finally {
this.preloadingPromises.delete(moduleName)
}
}
async doPreload(moduleName) {
const moduleMap = {
'Dashboard': () => import('./pages/Dashboard'),
'UserManagement': () => import('./pages/UserManagement'),
'ProductManagement': () => import('./pages/ProductManagement'),
'Reports': () => import('./pages/Reports'),
'Settings': () => import('./pages/Settings')
}
const importFunc = moduleMap[moduleName]
if (!importFunc) {
throw new Error(`未知的模块: ${moduleName}`)
}
return await importFunc()
}
// 清除预加载的模块
clear(moduleName) {
if (moduleName) {
this.preloadedModules.delete(moduleName)
this.preloadingPromises.delete(moduleName)
} else {
this.preloadedModules.clear()
this.preloadingPromises.clear()
}
}
}
const preloadManager = new PreloadManager()
// 智能懒加载组件
function SmartLazyLoad({
componentPath,
preloadOnHover = false,
preloadOnVisible = true,
fallback = <PageLoader />,
errorBoundary = LazyLoadErrorBoundary
}) {
const [Component, setComponent] = useState(null)
const [isLoading, setIsLoading] = useState(false)
const [error, setError] = useState(null)
const elementRef = useRef(null)
// 加载组件
const loadComponent = useCallback(async () => {
if (Component || isLoading) return
setIsLoading(true)
setError(null)
try {
const module = await import(componentPath)
setComponent(module.default || module)
} catch (err) {
setError(err)
console.error('组件加载失败:', err)
} finally {
setIsLoading(false)
}
}, [componentPath, Component, isLoading])
// 预加载
const preloadComponent = useCallback(async () => {
if (Component || isLoading) return
try {
const module = await import(componentPath)
// 预加载但不设置组件
console.log('组件预加载完成:', componentPath)
} catch (err) {
console.warn('组件预加载失败:', err)
}
}, [componentPath, Component, isLoading])
// 悬停预加载
const handleMouseEnter = useCallback(() => {
if (preloadOnHover) {
preloadComponent()
}
}, [preloadOnHover, preloadComponent])
// 可见性预加载
useEffect(() => {
if (!preloadOnVisible || !elementRef.current || Component) return
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadComponent()
observer.disconnect()
}
})
},
{ threshold: 0.1 }
)
observer.observe(elementRef.current)
return () => observer.disconnect()
}, [preloadOnVisible, Component, loadComponent])
const ErrorBoundaryComponent = errorBoundary
return (
<div ref={elementRef} onMouseEnter={handleMouseEnter}>
<ErrorBoundaryComponent>
{Component ? <Component /> : (error ? <div>加载失败</div> : fallback)}
</ErrorBoundaryComponent>
</div>
)
}
// 使用示例
function OptimizedRoutes() {
return (
<Routes>
<Route path="/" element={<Home />} />
{/* 懒加载带预加载 */}
<Route
path="/dashboard"
element={
<SmartLazyLoad
componentPath="./pages/Dashboard"
preloadOnHover={true}
preloadOnVisible={false}
/>
}
/>
<Route
path="/users"
element={
<SmartLazyLoad
componentPath="./pages/UserManagement"
preloadOnVisible={true}
/>
}
/>
{/* 批量懒加载 */}
<Route
path="/products"
element={
<Suspense fallback={<ProductListLoader />}>
<LazyProductManagement />
</Suspense>
}
/>
</Routes>
)
}
// 懒加载组 - 同时加载多个相关组件
const LazyProductManagement = lazy(() =>
Promise.all([
import('./pages/ProductManagement'),
import('./pages/ProductDetail'),
import('./pages/ProductEdit')
]).then(([productModule, detailModule, editModule]) => ({
default: productModule.default,
ProductDetail: detailModule.default,
ProductEdit: editModule.default
}))
)
6.7.2 路由级别的性能优化
路由渲染优化:
// 路由级别的 memo 优化
const MemoizedDashboard = memo(Dashboard, (prevProps, nextProps) => {
// 自定义比较函数
return prevProps.userId === nextProps.userId &&
prevProps.timestamp === nextProps.timestamp
})
// 使用 React.memo 优化路由组件
function OptimizedRoute({ component: Component, ...props }) {
const MemoizedComponent = useMemo(() => memo(Component), [Component])
return <MemoizedComponent {...props} />
}
// 路由渲染性能监控
function useRoutePerformance() {
const [metrics, setMetrics] = useState({
renderTime: 0,
loadTime: 0,
componentCount: 0
})
const measureRoutePerformance = useCallback((componentName) => {
const startTime = performance.now()
const startLoadTime = Date.now()
return {
onMount: () => {
const renderTime = performance.now() - startTime
const loadTime = Date.now() - startLoadTime
setMetrics(prev => ({
renderTime: Math.max(prev.renderTime, renderTime),
loadTime: Math.max(prev.loadTime, loadTime),
componentCount: prev.componentCount + 1
}))
// 发送到分析服务
analytics.track('route_performance', {
component: componentName,
renderTime,
loadTime
})
}
}
}, [])
return { metrics, measureRoutePerformance }
}
// 性能优化的路由组件
function PerformanceOptimizedRoute({
component: Component,
componentName,
...props
}) {
const { measureRoutePerformance } = useRoutePerformance()
const performanceHook = useMemo(() =>
measureRoutePerformance(componentName),
[componentName, measureRoutePerformance]
)
useEffect(() => {
performanceHook.onMount()
}, [performanceHook])
return <Component {...props} />
}
// 虚拟化长列表路由
function VirtualizedProductList() {
const [products, setProducts] = useState([])
const [loading, setLoading] = useState(true)
useEffect(() => {
// 虚拟化加载数据
const loadProducts = async () => {
setLoading(true)
// 首先加载可见区域的数据
const initialData = await fetchProducts({ page: 1, limit: 20 })
setProducts(initialData)
setLoading(false)
// 后台预加载更多数据
setTimeout(async () => {
const moreData = await fetchProducts({ page: 2, limit: 20 })
setProducts(prev => [...prev, ...moreData])
}, 1000)
}
loadProducts()
}, [])
if (loading && products.length === 0) {
return <ProductListSkeleton />
}
return (
<VirtualList
items={products}
itemHeight={200}
renderItem={({ item, index }) => (
<ProductCard key={item.id} product={item} index={index} />
)}
onEndReached={() => {
// 无限滚动加载更多
loadMoreProducts()
}}
/>
)
}
6.7.3 缓存优化策略
智能路由缓存:
// 路由组件缓存系统
class RouteComponentCache {
constructor() {
this.cache = new Map()
this.maxSize = 10
this.ttl = 5 * 60 * 1000 // 5分钟
}
// 获取缓存的组件
get(key) {
const item = this.cache.get(key)
if (!item) return null
// 检查是否过期
if (Date.now() - item.timestamp > this.ttl) {
this.cache.delete(key)
return null
}
// 更新访问时间(LRU)
item.lastAccessed = Date.now()
return item.component
}
// 缓存组件
set(key, component) {
// 如果缓存已满,删除最旧的条目
if (this.cache.size >= this.maxSize) {
let oldestKey = null
let oldestTime = Date.now()
for (const [cacheKey, item] of this.cache.entries()) {
if (item.lastAccessed < oldestTime) {
oldestTime = item.lastAccessed
oldestKey = cacheKey
}
}
if (oldestKey) {
this.cache.delete(oldestKey)
}
}
this.cache.set(key, {
component,
timestamp: Date.now(),
lastAccessed: Date.now()
})
}
// 清除缓存
clear(key) {
if (key) {
this.cache.delete(key)
} else {
this.cache.clear()
}
}
// 获取缓存统计
getStats() {
return {
size: this.cache.size,
maxSize: this.maxSize,
items: Array.from(this.cache.entries()).map(([key, item]) => ({
key,
age: Date.now() - item.timestamp,
lastAccessed: Date.now() - item.lastAccessed
}))
}
}
}
const routeComponentCache = new RouteComponentCache()
// 缓存优化的懒加载组件
function CachedLazyLoad({
cacheKey,
componentPath,
fallback = <PageLoader />
}) {
const [Component, setComponent] = useState(null)
const [isLoading, setIsLoading] = useState(false)
useEffect(() => {
const loadComponent = async () => {
// 检查缓存
const cachedComponent = routeComponentCache.get(cacheKey)
if (cachedComponent) {
setComponent(() => cachedComponent)
return
}
setIsLoading(true)
try {
const module = await import(componentPath)
const LoadedComponent = module.default || module
// 缓存组件
routeComponentCache.set(cacheKey, LoadedComponent)
setComponent(() => LoadedComponent)
} catch (error) {
console.error('组件加载失败:', error)
} finally {
setIsLoading(false)
}
}
loadComponent()
}, [cacheKey, componentPath])
if (Component) {
return <Component />
}
if (isLoading) {
return fallback
}
return <div>组件加载失败</div>
}
// 数据缓存和组件缓存结合
function useHybridCache() {
const [componentCache] = useState(() => new Map())
const [dataCache] = useState(() => new Map())
const cacheComponent = useCallback((key, component) => {
componentCache.set(key, component)
}, [componentCache])
const getCachedComponent = useCallback((key) => {
return componentCache.get(key)
}, [componentCache])
const cacheData = useCallback((key, data, ttl = 300000) => {
dataCache.set(key, {
data,
timestamp: Date.now(),
ttl
})
}, [dataCache])
const getCachedData = useCallback((key) => {
const item = dataCache.get(key)
if (!item) return null
if (Date.now() - item.timestamp > item.ttl) {
dataCache.delete(key)
return null
}
return item.data
}, [dataCache])
return {
cacheComponent,
getCachedComponent,
cacheData,
getCachedData
}
}
6.7.4 路由级别的性能监控
性能监控和分析:
// 路由性能监控器
class RoutePerformanceMonitor {
constructor() {
this.metrics = new Map()
this.observers = []
}
// 开始监控路由
startRouteMonitoring(routePath) {
const startTime = performance.now()
const startLoadTime = Date.now()
this.metrics.set(routePath, {
path: routePath,
startTime,
startLoadTime,
renderTime: null,
loadTime: null,
componentMountTime: null,
memoryBefore: performance.memory?.usedJSHeapSize || 0,
memoryAfter: null
})
// 监控资源加载
this.observeResourceLoading(routePath)
}
// 结束监控路由
endRouteMonitoring(routePath, componentMountTime = null) {
const metrics = this.metrics.get(routePath)
if (!metrics) return
metrics.renderTime = performance.now() - metrics.startTime
metrics.loadTime = Date.now() - metrics.startLoadTime
metrics.componentMountTime = componentMountTime
metrics.memoryAfter = performance.memory?.usedJSHeapSize || 0
// 计算性能指标
metrics.memoryDelta = metrics.memoryAfter - metrics.memoryBefore
// 发送性能数据
this.reportMetrics(metrics)
}
// 监控资源加载
observeResourceLoading(routePath) {
const observer = new PerformanceObserver((list) => {
const entries = list.getEntries()
const metrics = this.metrics.get(routePath)
if (!metrics) return
entries.forEach((entry) => {
if (entry.name.includes(routePath)) {
metrics.resourceTiming = metrics.resourceTiming || []
metrics.resourceTiming.push({
name: entry.name,
duration: entry.duration,
size: entry.transferSize
})
}
})
})
observer.observe({ entryTypes: ['resource'] })
this.observers.push(observer)
}
// 报告性能指标
reportMetrics(metrics) {
// 发送到分析服务
if (window.gtag) {
window.gtag('event', 'route_performance', {
custom_map: {
route_path: metrics.path,
render_time: metrics.renderTime,
load_time: metrics.loadTime,
memory_delta: metrics.memoryDelta
}
})
}
// 发送到自定义分析端点
fetch('/api/analytics/route-performance', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(metrics)
}).catch(error => {
console.warn('性能数据发送失败:', error)
})
// 本地日志记录
console.log('路由性能指标:', metrics)
}
// 获取性能统计
getPerformanceStats() {
const allMetrics = Array.from(this.metrics.values())
return {
totalRoutes: allMetrics.length,
averageRenderTime: allMetrics.reduce((sum, m) => sum + m.renderTime, 0) / allMetrics.length,
averageLoadTime: allMetrics.reduce((sum, m) => sum + m.loadTime, 0) / allMetrics.length,
slowestRoute: allMetrics.reduce((slowest, current) =>
current.renderTime > slowest.renderTime ? current : slowest
),
fastestRoute: allMetrics.reduce((fastest, current) =>
current.renderTime < fastest.renderTime ? current : fastest
)
}
}
}
const routePerformanceMonitor = new RoutePerformanceMonitor()
// 性能监控 Hook
function useRoutePerformanceMonitor(routePath) {
const location = useLocation()
useEffect(() => {
// 开始监控
routePerformanceMonitor.startRouteMonitoring(routePath)
// 组件挂载时间
const componentMountTime = performance.now()
return () => {
// 结束监控
routePerformanceMonitor.endRouteMonitoring(routePath, componentMountTime)
}
}, [routePath])
// 监控路由变化
useEffect(() => {
const handleRouteChange = () => {
// 清理之前的观察者
routePerformanceMonitor.observers.forEach(observer => {
observer.disconnect()
})
routePerformanceMonitor.observers = []
// 开始新的监控
routePerformanceMonitor.startRouteMonitoring(location.pathname)
}
return handleRouteChange
}, [location.pathname])
}
// 性能优化建议系统
function PerformanceOptimizer() {
const [recommendations, setRecommendations] = useState([])
const stats = routePerformanceMonitor.getPerformanceStats()
useEffect(() => {
const generateRecommendations = () => {
const recs = []
// 渲染时间过慢
if (stats.averageRenderTime > 1000) {
recs.push({
type: 'warning',
title: '平均渲染时间过长',
description: '建议使用 React.memo 和 useMemo 优化组件渲染',
action: 'implement_memoization'
})
}
// 加载时间过慢
if (stats.averageLoadTime > 3000) {
recs.push({
type: 'error',
title: '平均加载时间过长',
description: '建议实施代码分割和预加载策略',
action: 'implement_code_splitting'
})
}
// 内存使用异常
if (stats.slowestRoute?.memoryDelta > 10 * 1024 * 1024) {
recs.push({
type: 'warning',
title: '内存使用增长过快',
description: `${stats.slowestRoute.path} 路由可能导致内存泄漏`,
action: 'check_memory_leaks'
})
}
setRecommendations(recs)
}
generateRecommendations()
}, [stats])
return {
recommendations,
stats,
clearCache: () => routeComponentCache.clear(),
optimizeRoutes: () => implementOptimizations()
}
}
// 性能优化实施
function implementOptimizations() {
// 实施各种优化策略
console.log('正在实施性能优化...')
// 清理未使用的缓存
routeComponentCache.clear()
// 预加载关键路由
const criticalRoutes = ['/dashboard', '/products', '/users']
criticalRoutes.forEach(route => {
preloadManager.preload(getModuleNameFromPath(route))
})
// 压缩和优化资源
if ('requestIdleCallback' in window) {
requestIdleCallback(() => {
// 在空闲时进行优化
optimizeResources()
})
}
return true
}
// 使用示例
function PerformanceDashboard() {
const { recommendations, stats, clearCache } = PerformanceOptimizer()
return (
<div className="performance-dashboard">
<h2>路由性能监控</h2>
<div className="stats-grid">
<div className="stat-card">
<h3>总路由数</h3>
<p>{stats.totalRoutes}</p>
</div>
<div className="stat-card">
<h3>平均渲染时间</h3>
<p>{stats.averageRenderTime.toFixed(2)}ms</p>
</div>
<div className="stat-card">
<h3>平均加载时间</h3>
<p>{stats.averageLoadTime.toFixed(2)}ms</p>
</div>
</div>
<div className="recommendations">
<h3>优化建议</h3>
{recommendations.map((rec, index) => (
<div key={index} className={`recommendation ${rec.type}`}>
<h4>{rec.title}</h4>
<p>{rec.description}</p>
<button onClick={() => handleOptimization(rec.action)}>
实施优化
</button>
</div>
))}
</div>
<button onClick={clearCache} className="clear-cache-btn">
清除缓存
</button>
</div>
)
}
通过实施这些路由性能优化和懒加载策略,你可以显著提升应用的加载速度和用户体验。关键是要持续监控性能指标,并根据实际使用情况进行调整和优化。
6.8 高级路由模式和最佳实践
6.8.1 多种路由模式
Hash Router 模式:
// Hash Router 适用于特定场景
// 特点:兼容性好,不需要服务器配置,URL中包含#
import { HashRouter } from 'react-router-dom'
function HashRouterApp() {
return (
<HashRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/products" element={<Products />} />
</Routes>
</HashRouter>
)
}
// Hash Router 的实际应用场景
function StaticHostingApp() {
// 适用于 GitHub Pages、Netlify 等静态托管服务
return (
<HashRouter basename="/my-app">
<App />
</HashRouter>
)
}
// 动态选择路由器
function SmartRouter({ children }) {
const isStaticHosting = process.env.REACT_APP_STATIC_HOSTING === 'true'
const isDevelopment = process.env.NODE_ENV === 'development'
const Router = isStaticHosting || isDevelopment ? HashRouter : BrowserRouter
return (
<Router basename={process.env.PUBLIC_URL}>
{children}
</Router>
)
}
Memory Router 模式:
// Memory Router 用于测试和非浏览器环境
import { MemoryRouter } from 'react-router-dom'
// 测试环境使用
function TestApp() {
return (
<MemoryRouter initialEntries={['/', '/about', '/contact']}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/contact" element={<Contact />} />
</Routes>
</MemoryRouter>
)
}
// React Native 中使用
function NativeApp() {
return (
<MemoryRouter>
<App />
</MemoryRouter>
)
}
// 内存路由的实际应用 - 向导组件
function Wizard({ steps, onComplete }) {
const [currentStep, setCurrentStep] = useState(0)
const handleNext = () => {
if (currentStep < steps.length - 1) {
setCurrentStep(currentStep + 1)
} else {
onComplete()
}
}
const handlePrevious = () => {
if (currentStep > 0) {
setCurrentStep(currentStep - 1)
}
}
return (
<MemoryRouter
initialEntries={[`/step/${currentStep}`]}
index={currentStep}
>
<div className="wizard">
<Routes>
{steps.map((step, index) => (
<Route
key={index}
path={`/step/${index}`}
element={<step.component onNext={handleNext} onPrevious={handlePrevious} />}
/>
))}
</Routes>
<div className="wizard-progress">
{steps.map((_, index) => (
<div
key={index}
className={`step-indicator ${index <= currentStep ? 'active' : ''}`}
/>
))}
</div>
</div>
</MemoryRouter>
)
}
6.8.2 国际化路由
多语言路由配置:
// 国际化路由实现
import { useParams } from 'react-router-dom'
// 语言检测和处理
function useI18nRouting() {
const { lang } = useParams()
const navigate = useNavigate()
const location = useLocation()
// 支持的语言列表
const supportedLanguages = ['zh', 'en', 'ja', 'ko']
const defaultLanguage = 'zh'
// 获取当前语言
const getCurrentLanguage = useCallback(() => {
if (lang && supportedLanguages.includes(lang)) {
return lang
}
// 从浏览器语言检测
const browserLang = navigator.language.split('-')[0]
if (supportedLanguages.includes(browserLang)) {
return browserLang
}
return defaultLanguage
}, [lang])
// 切换语言
const switchLanguage = useCallback((newLang) => {
if (!supportedLanguages.includes(newLang)) {
console.warn(`不支持的语言: ${newLang}`)
return
}
// 构建新的路径
const currentPath = location.pathname
const pathWithoutLang = currentPath.replace(/^\/[a-z]{2}/, '') || '/'
navigate(`/${newLang}${pathWithoutLang}`, { replace: true })
}, [location.pathname, navigate])
// 获取带语言前缀的路径
const getLocalizedPath = useCallback((path, language = getCurrentLanguage()) => {
return `/${language}${path}`
}, [getCurrentLanguage])
return {
currentLanguage: getCurrentLanguage(),
switchLanguage,
getLocalizedPath,
supportedLanguages,
defaultLanguage
}
}
// 国际化路由配置
function I18nApp() {
return (
<Routes>
{/* 语言前缀路由 */}
<Route path="/:lang">
<Route index element={<Home />} />
<Route path="about" element={<About />} />
<Route path="products" element={<Products />} />
<Route path="contact" element={<Contact />} />
</Route>
{/* 重定向到默认语言 */}
<Route path="*" element={<LanguageRedirector />} />
</Routes>
)
}
// 语言重定向组件
function LanguageRedirector() {
const { getLocalizedPath } = useI18nRouting()
const navigate = useNavigate()
useEffect(() => {
const currentLang = navigator.language.split('-')[0]
navigate(getLocalizedPath('/', currentLang), { replace: true })
}, [getLocalizedPath, navigate])
return <div>重定向中...</div>
}
// 带语言感知的导航组件
function I18nNavigation() {
const { currentLanguage, switchLanguage, getLocalizedPath } = useI18nRouting()
return (
<nav className="i18n-nav">
<div className="nav-links">
<Link to={getLocalizedPath('/')}>
{currentLanguage === 'zh' ? '首页' : 'Home'}
</Link>
<Link to={getLocalizedPath('/about')}>
{currentLanguage === 'zh' ? '关于' : 'About'}
</Link>
<Link to={getLocalizedPath('/products')}>
{currentLanguage === 'zh' ? '产品' : 'Products'}
</Link>
</div>
<div className="language-switcher">
{['zh', 'en', 'ja'].map(lang => (
<button
key={lang}
onClick={() => switchLanguage(lang)}
className={`lang-btn ${currentLanguage === lang ? 'active' : ''}`}
>
{lang.toUpperCase()}
</button>
))}
</div>
</nav>
)
}
6.8.3 微前端路由
微前端路由架构:
// 主应用路由管理
class MicroFrontendRouter {
constructor() {
this.routes = new Map()
this.activeApps = new Set()
this.eventBus = new EventTarget()
}
// 注册微应用路由
registerRoutes(appName, routeConfig) {
this.routes.set(appName, routeConfig)
// 通知其他应用路由已注册
this.eventBus.dispatchEvent(new CustomEvent('routes-registered', {
detail: { appName, routes: routeConfig }
}))
}
// 获取所有路由
getAllRoutes() {
const allRoutes = []
for (const [appName, routeConfig] of this.routes.entries()) {
routeConfig.routes.forEach(route => {
allRoutes.push({
...route,
appName,
path: `/${appName}${route.path}`
})
})
}
return allRoutes
}
// 激活微应用
async activateApp(appName) {
if (this.activeApps.has(appName)) {
return
}
try {
// 动态加载微应用
const app = await loadMicroApp(appName)
this.activeApps.add(appName)
this.eventBus.dispatchEvent(new CustomEvent('app-activated', {
detail: { appName, app }
}))
} catch (error) {
console.error(`激活微应用 ${appName} 失败:`, error)
}
}
// 停用微应用
deactivateApp(appName) {
if (!this.activeApps.has(appName)) {
return
}
this.activeApps.delete(appName)
this.eventBus.dispatchEvent(new CustomEvent('app-deactivated', {
detail: { appName }
}))
}
}
const microRouter = new MicroFrontendRouter()
// 主应用路由配置
function MainAppRoutes() {
const [microRoutes, setMicroRoutes] = useState([])
useEffect(() => {
// 监听微应用路由注册
const handleRoutesRegistered = (event) => {
setMicroRoutes(microRouter.getAllRoutes())
}
microRouter.eventBus.addEventListener('routes-registered', handleRoutesRegistered)
return () => {
microRouter.eventBus.removeEventListener('routes-registered', handleRoutesRegistered)
}
}, [])
return (
<Routes>
{/* 主应用路由 */}
<Route path="/" element={<MainDashboard />} />
<Route path="/settings" element={<Settings />} />
{/* 微应用路由 */}
{microRoutes.map(route => (
<Route
key={`${route.appName}-${route.path}`}
path={route.path}
element={<MicroAppWrapper appName={route.appName} />}
/>
))}
</Routes>
)
}
// 微应用包装器
function MicroAppWrapper({ appName }) {
const [app, setApp] = useState(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
const loadApp = async () => {
try {
setLoading(true)
await microRouter.activateApp(appName)
const microApp = await loadMicroApp(appName)
setApp(microApp)
} catch (error) {
console.error(`加载微应用 ${appName} 失败:`, error)
} finally {
setLoading(false)
}
}
loadApp()
return () => {
microRouter.deactivateApp(appName)
}
}, [appName])
if (loading) {
return <div>加载微应用中...</div>
}
if (!app) {
return <div>微应用加载失败</div>
}
return <div id={`micro-app-${appName}`}>{app}</div>
}
// 微应用路由 Hook
function useMicroAppRouting(appName) {
const location = useLocation()
const navigate = useNavigate()
// 注册路由到主应用
useEffect(() => {
const routes = getMicroAppRoutes(appName)
microRouter.registerRoutes(appName, { routes })
}, [appName])
// 微应用内部导航
const microNavigate = useCallback((to, options = {}) => {
const fullPath = `/${appName}${to}`
navigate(fullPath, options)
}, [appName, navigate])
return {
microNavigate,
currentPath: location.pathname.replace(`/${appName}`, ''),
isMicroAppActive: location.pathname.startsWith(`/${appName}`)
}
}
6.8.4 路由最佳实践总结
代码组织最佳实践:
// 推荐的路由文件组织结构
src/
├── router/
│ ├── index.ts # 主路由配置
│ ├── routes.ts # 路由定义
│ ├── guards/ # 路由守卫
│ │ ├── AuthGuard.tsx
│ │ ├── RoleGuard.tsx
│ │ └── PermissionGuard.tsx
│ ├── layouts/ # 布局组件
│ │ ├── MainLayout.tsx
│ │ ├── AuthLayout.tsx
│ │ └── PublicLayout.tsx
│ ├── hooks/ # 路由相关 Hooks
│ │ ├── useRouteState.ts
│ │ ├── useRouteCache.ts
│ │ └── useRoutePermission.ts
│ ├── utils/ # 路由工具函数
│ │ ├── routeHelpers.ts
│ │ ├── routeCache.ts
│ │ └── routeAnalytics.ts
│ └── types/ # TypeScript 类型定义
│ └── routeTypes.ts
├── pages/ # 页面组件
│ ├── Dashboard/
│ ├── Users/
│ └── Products/
└── components/ # 通用组件
// 路由定义的最佳实践
// routes.ts
export const routeConfig: RouteConfig[] = [
{
path: '/',
element: <PublicLayout />,
children: [
{
index: true,
element: <Home />,
meta: {
title: '首页',
description: '欢迎来到我们的应用'
}
},
{
path: 'about',
element: <About />,
meta: {
title: '关于我们',
description: '了解更多关于我们的信息'
}
}
]
},
{
path: '/app',
element: <AuthGuard><MainLayout /></AuthGuard>,
children: [
{
index: true,
element: <Dashboard />,
meta: {
title: '控制台',
requiresAuth: true,
permissions: ['dashboard.view']
}
},
{
path: 'users',
element: <RoleGuard roles={['admin', 'user_manager']}><UserManagement /></RoleGuard>,
children: [
{
index: true,
element: <UserList />,
meta: {
title: '用户管理',
permissions: ['users.view']
}
}
]
}
]
}
]
// 路由类型的最佳实践
// types/routeTypes.ts
export interface RouteMeta {
title?: string
description?: string
requiresAuth?: boolean
roles?: string[]
permissions?: string[]
preload?: boolean
cache?: boolean
analytics?: boolean
}
export interface RouteConfig {
path: string
element: React.ReactNode
children?: RouteConfig[]
meta?: RouteMeta
loader?: (params: any) => Promise<any>
errorElement?: React.ReactNode
caseSensitive?: boolean
}
// 路由配置的最佳实践函数
export function createRoute(config: RouteConfig): RouteObject {
const route: RouteObject = {
path: config.path,
element: config.element
}
if (config.children && config.children.length > 0) {
route.children = config.children.map(createRoute)
}
if (config.loader) {
route.loader = config.loader
}
if (config.errorElement) {
route.errorElement = config.errorElement
}
return route
}
性能最佳实践:
// 路由性能优化的最佳实践
// 1. 智能代码分割
const SmartLazyRoute = ({
componentPath,
preload = false,
fallback = <PageLoader />
}) => {
const Component = useMemo(() => {
return lazy(() => import(componentPath))
}, [componentPath])
// 预加载
useEffect(() => {
if (preload) {
import(componentPath)
}
}, [componentPath, preload])
return (
<Suspense fallback={fallback}>
<Component />
</Suspense>
)
}
// 2. 路由级别的错误处理
class RouteErrorBoundary extends React.Component {
state = { hasError: false, error: null, errorInfo: null }
static getDerivedStateFromError(error) {
return { hasError: true }
}
componentDidCatch(error, errorInfo) {
this.setState({ error, errorInfo })
// 发送错误报告
this.reportError(error, errorInfo)
}
reportError = (error, errorInfo) => {
fetch('/api/errors', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
error: error.message,
stack: error.stack,
componentStack: errorInfo.componentStack,
url: window.location.href,
timestamp: new Date().toISOString()
})
})
}
render() {
if (this.state.hasError) {
return (
<ErrorPage
error={this.state.error}
onRetry={() => this.setState({ hasError: false })}
/>
)
}
return this.props.children
}
}
// 3. 路由缓存的最佳实践
function useRouteCache() {
const cacheRef = useRef(new Map())
const [cacheStats, setCacheStats] = useState({ hits: 0, misses: 0 })
const getCachedRoute = useCallback((key) => {
const cached = cacheRef.current.get(key)
if (cached) {
setCacheStats(prev => ({ ...prev, hits: prev.hits + 1 }))
return cached
}
setCacheStats(prev => ({ ...prev, misses: prev.misses + 1 }))
return null
}, [])
const setCachedRoute = useCallback((key, data) => {
cacheRef.current.set(key, {
data,
timestamp: Date.now(),
hits: 0
})
}, [])
return { getCachedRoute, setCachedRoute, cacheStats }
}
安全最佳实践:
// 路由安全的最佳实践
// 1. 输入验证和清理
function validateRoutePath(path) {
// 防止路径遍历攻击
if (path.includes('..') || path.includes('~')) {
throw new Error('无效的路径')
}
// 验证路径格式
const validPathRegex = /^\/[a-zA-Z0-9\-_/]*$/
if (!validPathRegex.test(path)) {
throw new Error('路径格式无效')
}
return true
}
// 2. 安全的路由导航
function useSecureNavigation() {
const navigate = useNavigate()
const secureNavigate = useCallback((to, options = {}) => {
try {
validateRoutePath(to)
navigate(to, options)
} catch (error) {
console.error('导航失败:', error)
navigate('/404', { replace: true })
}
}, [navigate])
return secureNavigate
}
// 3. XSS 防护
function sanitizeRouteParams(params) {
const sanitized = {}
Object.entries(params).forEach(([key, value]) => {
// 清理潜在的 XSS 攻击
sanitized[key] = String(value)
.replace(/[<>]/g, '') // 移除尖括号
.replace(/javascript:/gi, '') // 移除 javascript: 协议
.replace(/on\w+=/gi, '') // 移除事件处理器
})
return sanitized
}
// 4. CSRF 防护
function useCSRFProtection() {
const [csrfToken, setCsrfToken] = useState('')
useEffect(() => {
// 获取 CSRF token
fetch('/api/csrf-token')
.then(res => res.json())
.then(data => setCsrfToken(data.token))
.catch(console.error)
}, [])
const secureFetch = useCallback(async (url, options = {}) => {
const secureOptions = {
...options,
headers: {
...options.headers,
'X-CSRF-Token': csrfToken,
'Content-Type': 'application/json'
}
}
return fetch(url, secureOptions)
}, [csrfToken])
return secureFetch
}
监控和调试最佳实践:
// 路由监控和调试的最佳实践
// 1. 路由分析工具
function useRouteAnalytics() {
const location = useLocation()
const [analytics, setAnalytics] = useState({
routeVisits: new Map(),
averageLoadTime: 0,
errorRate: 0
})
useEffect(() => {
const startTime = performance.now()
return () => {
const loadTime = performance.now() - startTime
setAnalytics(prev => {
const visits = new Map(prev.routeVisits)
visits.set(location.pathname, (visits.get(location.pathname) || 0) + 1)
return {
...prev,
routeVisits: visits,
averageLoadTime: (prev.averageLoadTime + loadTime) / 2
}
})
}
}, [location.pathname])
return analytics
}
// 2. 路由调试工具
function RouteDebugger({ enabled = process.env.NODE_ENV === 'development' }) {
const location = useLocation()
const [debugInfo, setDebugInfo] = useState(null)
useEffect(() => {
if (!enabled) return
const info = {
currentPath: location.pathname,
searchParams: Object.fromEntries(new URLSearchParams(location.search)),
state: location.state,
timestamp: new Date().toISOString()
}
setDebugInfo(info)
// 在控制台输出调试信息
console.group('🔍 Route Debug Info')
console.log('Path:', info.currentPath)
console.log('Search Params:', info.searchParams)
console.log('State:', info.state)
console.groupEnd()
}, [location, enabled])
if (!enabled || !debugInfo) return null
return (
<div className="route-debugger" style={{
position: 'fixed',
bottom: 10,
right: 10,
background: 'rgba(0,0,0,0.8)',
color: 'white',
padding: '10px',
borderRadius: '5px',
fontSize: '12px',
zIndex: 9999
}}>
<div>Path: {debugInfo.currentPath}</div>
<div>Time: {debugInfo.timestamp}</div>
</div>
)
}
// 3. 性能监控仪表板
function RoutePerformanceDashboard() {
const analytics = useRouteAnalytics()
return (
<div className="performance-dashboard">
<h3>路由性能监控</h3>
<div className="metrics">
<div>平均加载时间: {analytics.averageLoadTime.toFixed(2)}ms</div>
<div>错误率: {(analytics.errorRate * 100).toFixed(2)}%</div>
</div>
<div className="route-visits">
<h4>页面访问统计</h4>
{Array.from(analytics.routeVisits.entries()).map(([path, count]) => (
<div key={path}>
{path}: {count} 次
</div>
))}
</div>
</div>
)
}
通过遵循这些最佳实践,你可以构建出高质量、高性能、安全可靠的路由系统。记住,路由是应用的核心部分,合理的架构设计和持续的优化维护是确保应用长期稳定运行的关键。
🎉 第6章 学习总结
✅ 完成任务清单
恭喜!你已经成功完成了第6章"路由与导航详解"的全部学习任务:
- ✅ 基础概念与安装 - 理解单页应用路由原理,掌握React Router v6的核心概念
- ✅ 路由配置与导航 - 熟练使用BrowserRouter、Routes、Route等核心组件
- ✅ 动态路由与参数 - 掌握路径参数和查询参数的处理方法
- ✅ 嵌套路由与布局 - 设计复杂的嵌套路由结构和布局系统
- ✅ 权限控制与路由守卫 - 实现基于角色的路由权限控制机制
- ✅ 状态管理与数据获取 - 集成路由状态与数据预加载策略
- ✅ 性能优化与懒加载 - 实施路由组件的代码分割和缓存优化
- ✅ 高级模式与最佳实践 - 掌握多种路由模式和开发最佳实践
📚 核心知识点回顾
🎯 路由核心概念
- 单页应用路由原理:理解前端路由与后端路由的区别,掌握History API和Hash模式
- React Router v6新特性:熟悉新的路由匹配算法、Outlet组件、useNavigate Hook等
- 路由器选择:根据应用需求选择BrowserRouter、HashRouter或MemoryRouter
🔧 路由配置技术
- 声明式路由配置:使用Routes和Route组件构建路由结构
- 导航组件:熟练使用Link、NavLink进行声明式导航
- 编程式导航:掌握useNavigate Hook进行复杂导航逻辑
📊 参数处理策略
- 路径参数:处理动态路由参数,包括参数验证和类型转换
- 查询参数:使用useSearchParams Hook管理URL查询参数
- 参数同步:实现路由参数与应用状态的双向同步
🏗️ 嵌套路由架构
- Outlet组件:理解并使用Outlet渲染子路由内容
- 相对路径:掌握相对路径在嵌套路由中的应用
- 布局设计:创建可复用的布局组件系统
🔒 权限控制体系
- 认证守卫:实现基于用户身份的路由保护
- 角色控制:设计基于角色的访问控制系统
- 权限审计:记录和监控权限使用情况
⚡ 性能优化方案
- 代码分割:使用React.lazy实现组件级别的代码分割
- 懒加载:实施智能预加载和按需加载策略
- 缓存机制:设计路由组件和数据的缓存系统
🌐 高级路由模式
- 国际化路由:实现多语言路由支持
- 微前端路由:设计微前端架构中的路由管理
- 路由监控:建立完善的路由性能监控系统
🛠️ 实用工具和函数
路由管理工具
// 智能路由缓存
const routeCache = new RouteDataCache()
// 权限检查工具
const { hasPermission, hasRole } = usePermissions()
// 路由性能监控
const { measureRoutePerformance } = useRoutePerformance()
最佳实践模式
// 带错误边界的懒加载
<SmartLazyLoad
componentPath="./pages/Dashboard"
preloadOnHover={true}
errorBoundary={LazyLoadErrorBoundary}
/>
// 权限感知的路由守卫
<ProtectedRoute
requireAuth={true}
requiredRoles={['admin']}
requiredPermissions={['users.view']}
>
<UserManagement />
</ProtectedRoute>
🎯 学习成果应用
通过本章学习,你现在具备了以下能力:
- 架构设计能力:能够设计复杂的单页应用路由架构
- 性能优化能力:能够实施路由级别的性能优化策略
- 安全控制能力:能够构建完善的权限控制系统
- 问题解决能力:能够诊断和解决复杂的路由相关问题
- 最佳实践能力:能够遵循业界最佳实践进行开发
🚀 下一步学习建议
- 实践项目:构建一个完整的React应用,应用本章学到的所有路由技术
- 深入研究:探索React Router的源码,理解其内部实现原理
- 扩展学习:学习状态管理库(Redux Toolkit)与路由的集成
- 性能监控:建立完整的前端性能监控体系
- 架构设计:学习微前端架构中的路由管理策略
💡 核心心得
- 路由是应用的骨架:良好的路由架构是高质量应用的基础
- 性能优化很重要:合理使用懒加载和缓存能显著提升用户体验
- 安全不可忽视:完善的权限控制是生产环境的必要条件
- 监控和调试:完善的监控体系有助于及时发现和解决问题
- 持续优化:路由系统需要根据业务发展持续优化和改进
继续加油!你已经掌握了React路由的核心技术,现在可以开始构建专业级的React应用了!🎉

浙公网安备 33010602011771号