React-Router 原理与工作方式

React-Router 的路由模式、实现原理和简易实现

问题

React-Router 的实现原理及工作方式分别是什么?

解答

React-Router 基于两种路由模式实现:Hash 模式和 History 模式。它通过监听 URL 变化,匹配对应的路由配置,渲染相应组件。

两种路由模式

1. Hash 模式

URL 中带 #,如 http://example.com/#/home。通过监听 hashchange 事件实现。

// Hash 模式原理
window.addEventListener('hashchange', () => {
  const hash = window.location.hash.slice(1) // 去掉 #
  console.log('当前路径:', hash)
})

// 改变 hash
window.location.hash = '/about'

2. History 模式

URL 无 #,如 http://example.com/home。使用 HTML5 History API 实现。

// History 模式原理
window.addEventListener('popstate', (event) => {
  console.log('当前路径:', window.location.pathname)
})

// 跳转页面(不刷新)
history.pushState({ page: 'about' }, '', '/about')

// 替换当前记录
history.replaceState({ page: 'home' }, '', '/home')

简易 Router 实现

import React, { createContext, useContext, useState, useEffect } from 'react'

// 创建路由上下文
const RouterContext = createContext()

// BrowserRouter 组件
function BrowserRouter({ children }) {
  const [pathname, setPathname] = useState(window.location.pathname)

  useEffect(() => {
    // 监听浏览器前进后退
    const handlePopState = () => {
      setPathname(window.location.pathname)
    }
    window.addEventListener('popstate', handlePopState)
    return () => window.removeEventListener('popstate', handlePopState)
  }, [])

  // 导航函数
  const navigate = (to) => {
    history.pushState(null, '', to)
    setPathname(to)
  }

  return (
    <RouterContext.Provider value={{ pathname, navigate }}>
      {children}
    </RouterContext.Provider>
  )
}

// Route 组件
function Route({ path, element }) {
  const { pathname } = useContext(RouterContext)
  // 简单匹配,实际需要更复杂的匹配逻辑
  return pathname === path ? element : null
}

// Routes 组件
function Routes({ children }) {
  const { pathname } = useContext(RouterContext)
  
  // 遍历子元素找到匹配的路由
  let match = null
  React.Children.forEach(children, (child) => {
    if (!match && child.props.path === pathname) {
      match = child
    }
  })
  
  return match
}

// Link 组件
function Link({ to, children }) {
  const { navigate } = useContext(RouterContext)

  const handleClick = (e) => {
    e.preventDefault() // 阻止默认跳转
    navigate(to)
  }

  return <a href={to} onClick={handleClick}>{children}</a>
}

// useNavigate Hook
function useNavigate() {
  const { navigate } = useContext(RouterContext)
  return navigate
}

// 使用示例
function App() {
  return (
    <BrowserRouter>
      <nav>
        <Link to="/">首页</Link>
        <Link to="/about">关于</Link>
      </nav>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
      </Routes>
    </BrowserRouter>
  )
}

路由匹配原理

React-Router v6 使用路径评分机制匹配路由:

// 简化的路径匹配函数
function matchPath(pattern, pathname) {
  // 将路由模式转为正则
  // /users/:id -> /users/([^/]+)
  const regexPattern = pattern
    .replace(/:\w+/g, '([^/]+)')  // 动态参数
    .replace(/\*/g, '.*')         // 通配符
  
  const regex = new RegExp(`^${regexPattern}$`)
  const match = pathname.match(regex)
  
  if (!match) return null
  
  // 提取参数
  const paramNames = [...pattern.matchAll(/:(\w+)/g)].map(m => m[1])
  const params = {}
  paramNames.forEach((name, index) => {
    params[name] = match[index + 1]
  })
  
  return { params }
}

// 测试
matchPath('/users/:id', '/users/123')
// { params: { id: '123' } }

工作流程

URL 变化

监听事件 (popstate / hashchange)

更新 RouterContext 中的 pathname

触发组件重新渲染

Routes 遍历子 Route,匹配 path

渲染匹配的组件

关键点

  • Hash 模式:URL 带 #,兼容性好,通过 hashchange 事件监听
  • History 模式:URL 无 #,需要服务器配置支持,通过 popstate 事件监听
  • Context 传递:使用 React Context 在组件树中传递路由状态和导航方法
  • Link 组件:阻止 <a> 默认行为,使用 pushState 实现无刷新跳转
  • 路由匹配:将路径模式转为正则表达式,支持动态参数和通配符