33. 常用的 React 路由库?(React Router) 原理?

19 阅读4分钟

33. 常用的 React 路由库?(React Router) 原理?

答案:

常用的 React 路由库:

1. React Router

- 最流行和官方推荐的路由库 - 支持浏览器、服务端和 React Native - 声明式路由定义 - 支持动态路由、嵌套路由、路由守卫等

2. Reach Router

- 已被 React Router 合并 - 更简洁的 API 设计 - 更好的可访问性支持

3. Next.js Router

- Next.js 内置的基于文件系统的路由 - 自动代码分割 - 支持静态生成和服务端渲染

4. Wouter

- 轻量级路由库(约 1.5KB) - 简单的 Hook-based API - 适合小型项目

React Router 原理详解:

1. 基本概念

import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom'

function App() {
  return (
    <Router>
      <nav>
        <Link to="/">Home</Link>
        <Link to="/about">About</Link>
        <Link to="/users">Users</Link>
      </nav>

      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/users" element={<Users />} />
      </Routes>
    </Router>
  )
}

2. History API 原理

React Router 基于浏览器的 History API 实现,主要使用以下 API:

// 浏览器历史记录操作
history.pushState(state, title, url) // 添加新的历史记录
history.replaceState(state, title, url) // 替换当前历史记录
history.go(delta) // 前进或后退
history.back() // 后退
history.forward() // 前进

// 监听浏览器前进后退
window.addEventListener('popstate', (event) => {
  // 处理路由变化
})

3. Router 组件类型

// BrowserRouter - 使用 HTML5 history API
import { BrowserRouter } from 'react-router-dom'
function App() {
  return <BrowserRouter>{/* 路由配置 */}</BrowserRouter>
}

// HashRouter - 使用 URL hash
import { HashRouter } from 'react-router-dom'
function App() {
  return <HashRouter>{/* 路由配置 */}</HashRouter>
}

// MemoryRouter - 在内存中管理历史记录
import { MemoryRouter } from 'react-router-dom'
function App() {
  return <MemoryRouter>{/* 路由配置 */}</MemoryRouter>
}

4. 路由匹配原理

// 路由配置
;<Routes>
  <Route path="/" element={<Home />} />
  <Route path="/users" element={<Users />} />
  <Route path="/users/:id" element={<User />} />
  <Route path="/users/:id/posts/:postId" element={<UserPost />} />
  <Route path="*" element={<NotFound />} />
</Routes>

// 路由匹配算法
function matchRoutes(routes, pathname) {
  for (let route of routes) {
    const match = matchPath(route.path, pathname)
    if (match) {
      return { route, match }
    }
  }
  return null
}

// 路径匹配函数
function matchPath(pattern, pathname) {
  const keys = []
  const regex = pathToRegexp(pattern, keys)
  const match = regex.exec(pathname)

  if (!match) return null

  const params = {}
  keys.forEach((key, index) => {
    params[key.name] = match[index + 1]
  })

  return { params, pathname, pattern }
}

5. 动态路由和参数

// 路由参数
function UserProfile() {
  const { id } = useParams() // 获取 :id 参数
  const location = useLocation() // 获取当前位置信息
  const navigate = useNavigate() // 编程式导航

  return (
    <div>
      <h1>User Profile: {id}</h1>
      <button onClick={() => navigate('/users')}>Back to Users</button>
    </div>
  )
}

// 查询参数
function SearchResults() {
  const [searchParams, setSearchParams] = useSearchParams()
  const query = searchParams.get('q')
  const page = searchParams.get('page') || '1'

  const handleSearch = (newQuery) => {
    setSearchParams({ q: newQuery, page: '1' })
  }

  return (
    <div>
      <h1>Search Results for: {query}</h1>
      <p>Page: {page}</p>
    </div>
  )
}

6. 嵌套路由

// 嵌套路由配置
function App() {
  return (
    <Router>
      <Routes>
        <Route path="/" element={<Layout />}>
          <Route index element={<Home />} />
          <Route path="users" element={<Users />}>
            <Route index element={<UsersList />} />
            <Route path=":id" element={<UserProfile />} />
            <Route path=":id/posts" element={<UserPosts />} />
          </Route>
          <Route path="*" element={<NotFound />} />
        </Route>
      </Routes>
    </Router>
  )
}

// Layout 组件
function Layout() {
  return (
    <div>
      <header>Navigation</header>
      <main>
        <Outlet /> {/* 渲染子路由 */}
      </main>
      <footer>Footer</footer>
    </div>
  )
}

// Users 组件
function Users() {
  return (
    <div>
      <h1>Users</h1>
      <Outlet /> {/* 渲染子路由 */}
    </div>
  )
}

7. 路由守卫和权限控制

// 路由守卫组件
function ProtectedRoute({ children, redirectTo = '/login' }) {
  const { user } = useAuth()
  const location = useLocation()

  if (!user) {
    return <Navigate to={redirectTo} state={{ from: location }} replace />
  }

  return children
}

// 角色权限守卫
function RoleGuard({ children, requiredRole, fallback = <Unauthorized /> }) {
  const { user } = useAuth()

  if (!user || !user.roles.includes(requiredRole)) {
    return fallback
  }

  return children
}

// 使用守卫
function App() {
  return (
    <Router>
      <Routes>
        <Route path="/login" element={<Login />} />
        <Route
          path="/dashboard"
          element={
            <ProtectedRoute>
              <Dashboard />
            </ProtectedRoute>
          }
        />
        <Route
          path="/admin"
          element={
            <ProtectedRoute>
              <RoleGuard requiredRole="admin">
                <AdminPanel />
              </RoleGuard>
            </ProtectedRoute>
          }
        />
      </Routes>
    </Router>
  )
}

8. 编程式导航

function NavigationExample() {
  const navigate = useNavigate()
  const location = useLocation()

  const handleSubmit = async (formData) => {
    try {
      await submitData(formData)
      // 导航到成功页面
      navigate('/success', {
        state: { message: 'Data submitted successfully' },
      })
    } catch (error) {
      // 导航到错误页面
      navigate('/error', {
        state: { error: error.message },
      })
    }
  }

  const goBack = () => {
    navigate(-1) // 返回上一页
  }

  const goHome = () => {
    navigate('/', { replace: true }) // 替换当前历史记录
  }

  return (
    <div>
      <button onClick={goBack}>Go Back</button>
      <button onClick={goHome}>Go Home</button>
      <form onSubmit={handleSubmit}>{/* 表单内容 */}</form>
    </div>
  )
}

9. 数据加载和 Loader

// React Router v6.4+ 的数据加载
import { createBrowserRouter, RouterProvider } from 'react-router-dom'

// 数据加载函数
async function userLoader({ params }) {
  const user = await fetch(`/api/users/${params.id}`)
  if (!user.ok) {
    throw new Response('User not found', { status: 404 })
  }
  return user.json()
}

// 路由配置
const router = createBrowserRouter([
  {
    path: '/',
    element: <Layout />,
    children: [
      {
        path: 'users/:id',
        element: <UserProfile />,
        loader: userLoader,
        errorElement: <UserError />,
      },
    ],
  },
])

// 组件中使用数据
function UserProfile() {
  const user = useLoaderData() // 获取 loader 加载的数据

  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
    </div>
  )
}

// 应用根组件
function App() {
  return <RouterProvider router={router} />
}

10. 代码分割和懒加载

import { lazy, Suspense } from 'react'

// 懒加载组件
const Dashboard = lazy(() => import('./Dashboard'))
const UserProfile = lazy(() => import('./UserProfile'))
const AdminPanel = lazy(() => import('./AdminPanel'))

function App() {
  return (
    <Router>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/dashboard" element={<Dashboard />} />
          <Route path="/users/:id" element={<UserProfile />} />
          <Route path="/admin" element={<AdminPanel />} />
        </Routes>
      </Suspense>
    </Router>
  )
}

// 带有加载状态的组件
function LazyRoute({ children, fallback = <LoadingSpinner /> }) {
  return <Suspense fallback={fallback}>{children}</Suspense>
}

11. 路由状态管理

// 自定义路由状态 Hook
function useRouteState() {
  const location = useLocation()
  const navigate = useNavigate()

  const setRouteState = useCallback(
    (state) => {
      navigate(location.pathname, {
        state: { ...location.state, ...state },
        replace: true,
      })
    },
    [location, navigate]
  )

  return [location.state, setRouteState]
}

// 使用路由状态
function UsersList() {
  const [routeState, setRouteState] = useRouteState()
  const [searchParams, setSearchParams] = useSearchParams()

  const currentPage = parseInt(searchParams.get('page') || '1')
  const searchQuery = searchParams.get('q') || ''

  const handlePageChange = (page) => {
    setSearchParams({ ...Object.fromEntries(searchParams), page })
  }

  const handleSearch = (query) => {
    setSearchParams({ q: query, page: '1' })
  }

  return (
    <div>
      <SearchBox onSearch={handleSearch} value={searchQuery} />
      <UserList page={currentPage} />
      <Pagination current={currentPage} onChange={handlePageChange} />
    </div>
  )
}

12. 自定义 Router

// 简化版 Router 实现
class SimpleRouter {
  constructor() {
    this.routes = []
    this.currentPath = window.location.pathname

    // 监听浏览器前进后退
    window.addEventListener('popstate', () => {
      this.currentPath = window.location.pathname
      this.render()
    })
  }

  addRoute(path, component) {
    this.routes.push({ path, component })
  }

  navigate(path) {
    window.history.pushState({}, '', path)
    this.currentPath = path
    this.render()
  }

  render() {
    const route = this.routes.find((route) => {
      return this.matchPath(route.path, this.currentPath)
    })

    if (route) {
      const container = document.getElementById('app')
      container.innerHTML = route.component()
    }
  }

  matchPath(pattern, path) {
    // 简单的路径匹配逻辑
    return pattern === path
  }
}

// 使用自定义 Router
const router = new SimpleRouter()
router.addRoute('/', () => '<h1>Home</h1>')
router.addRoute('/about', () => '<h1>About</h1>')
router.render()

工作原理总结:

1. 历史记录管理

- 使用 History API 管理浏览器历史记录 - 监听 popstate 事件处理前进后退 - 通过 pushStatereplaceState 更新 URL

2. 路由匹配

- 将路由配置转换为正则表达式 - 按优先级匹配当前路径 - 提取路径参数和查询参数

3. 组件渲染

- 根据匹配结果渲染对应组件 - 支持嵌套路由和组件组合 - 通过 Context 传递路由信息

4. 导航控制

- 提供声明式和编程式导航 - 支持路由守卫和权限控制 - 处理路由状态和数据传递

总结:

- React Router 是最主流的 React 路由解决方案 - 核心原理 基于 History API 和组件渲染 - 主要特性 包括动态路由、嵌套路由、路由守卫、数据加载等 - 性能优化 支持代码分割和懒加载 - 最佳实践 包括合理的路由设计、权限控制、状态管理等