React路由与导航详解

第6章 路由与导航详解

🎯 章节学习目标

本章将深入讲解React应用中的路由与导航技术,从基础的React Router使用到高级的路由管理模式。通过学习本章,你将掌握单页应用的路由设计、权限控制、性能优化等核心技术。

📋 学习任务清单

✅ 基础概念与安装

✅ 路由配置与导航

✅ 动态路由与参数

✅ 嵌套路由与布局

✅ 权限控制与路由守卫

✅ 状态管理与数据获取

✅ 性能优化与懒加载

✅ 高级模式与最佳实践

🚀 核心知识点概览

  1. React Router生态系统 - 理解react-router和react-router-dom的关系
  2. 路由配置模式 - 掌握声明式和编程式路由配置
  3. 导航生命周期 - 理解路由切换的完整流程
  4. 参数处理机制 - 掌握路径参数和查询参数的处理
  5. 嵌套路由设计 - 学会复杂应用的路由架构设计
  6. 权限控制体系 - 实现细粒度的路由权限管理
  7. 性能优化策略 - 优化路由相关的应用性能
  8. 调试与测试 - 掌握路由相关的调试和测试技术

💡 学习建议

  • 循序渐进:从基础概念开始,逐步深入到高级特性
  • 实践为主:每个知识点都要通过代码实践来巩固
  • 案例驱动:通过完整的项目案例来理解整体设计
  • 性能意识:在学习和实践中始终关注性能影响
  • 最佳实践:学习业界成熟的路由设计模式

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 || []
  }
}

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>&copy; 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>

🎯 学习成果应用

通过本章学习,你现在具备了以下能力:

  1. 架构设计能力:能够设计复杂的单页应用路由架构
  2. 性能优化能力:能够实施路由级别的性能优化策略
  3. 安全控制能力:能够构建完善的权限控制系统
  4. 问题解决能力:能够诊断和解决复杂的路由相关问题
  5. 最佳实践能力:能够遵循业界最佳实践进行开发

🚀 下一步学习建议

  1. 实践项目:构建一个完整的React应用,应用本章学到的所有路由技术
  2. 深入研究:探索React Router的源码,理解其内部实现原理
  3. 扩展学习:学习状态管理库(Redux Toolkit)与路由的集成
  4. 性能监控:建立完整的前端性能监控体系
  5. 架构设计:学习微前端架构中的路由管理策略

💡 核心心得

  • 路由是应用的骨架:良好的路由架构是高质量应用的基础
  • 性能优化很重要:合理使用懒加载和缓存能显著提升用户体验
  • 安全不可忽视:完善的权限控制是生产环境的必要条件
  • 监控和调试:完善的监控体系有助于及时发现和解决问题
  • 持续优化:路由系统需要根据业务发展持续优化和改进

继续加油!你已经掌握了React路由的核心技术,现在可以开始构建专业级的React应用了!🎉


posted @ 2025-11-29 18:19  seven3306  阅读(2)  评论(0)    收藏  举报