手写react-router,理解react-router原理

一. react-router介绍

react-routerReact框架配套的路由库,支持配置路由与组件的映射关系。

代码示例如下

javascript 复制代码
function Home() {
  return <h1>Home</h1>
}

function Account() {
  return <h1>Account</h1>
}

function App() {
  return (
    <BrowserRouter>
      <div>
        <ul>
          <li>
            <Link to='/'>home</Link>
          </li>
          <li>
            <Link to='/account'>account</Link>
          </li>
        </ul>
        <Routes>
          <Route path='/' element={<Home />} />
          <Route path='/account' element={<Account />} />d 
        </Routes>
      </div>
    </BrowserRouter>
  )
}

二. 实现react-router

react-router核心原理是采用路由劫持,即监听windowpopstate事件,当页面路由变更时会触发popstate事件,执行路由组件渲染逻辑。

2.1 BrowserRouter

BrowserRouter组件的主要逻辑是负责初始化路由劫持逻辑,当路由变更时触发组件更新渲染。触发组件更新渲染主要通过ReactuseSyncExternalStore方法实现,在往期文章手写React useSyncExternalStore,理解useSyncExternalStore原理有介绍其实现原理,本文不再赘述。

javascript 复制代码
function BrowserRouter({ children }) {
  const historyRef = useRef()

  if (historyRef.current === null) historyRef.current = createBrowserHistory()

  const history = historyRef.current

  const { action, location } = useSyncExternalStore(
    history.subscribe,
    history.getSnapshot,
  )

  return (
    <Router location={location} navigator={history} navigationType={action}>
      {children}
    </Router>
  )
}

2.1.1 createBrowserHistory

创建自定义history对象实例。listener变量用于记录触发组件更新渲染的方法,当路由变更时,会调用该方法触发更新渲染。

javascript 复制代码
const Action = {
  Pop: 'Pop',
  Push: 'Push',
  Replace: 'Replace',
}

function createBrowserHistory() {
  let action = Action.Pop
  let listener = null

  const handlePop = () => {}

  const history = {
    get action() {
      return action
    },
    get location() {
      const { pathname, search, hash } = window.location
      return {
        pathname,
        search,
        hash,
      }
    },
    push: to => {
      action = Action.Push
      window.history.pushState(null, '', to)
      listener()
    },
    subscribe: callback => {
      listener = callback
      window.addEventListener('popstate', handlePop)
      return () => {
        window.removeEventListener('popstate', handlePop)
        listener = null
      }
    },
    getSnapshot: () => {
      return {
        action: history.action,
        location: history.location,
      }
    },
  }

  return history
}

2.1.2 Router

Router组件逻辑比较简单,主要是添加两个Context组件包括子组件,便于子组件通过useContext拿到传递的属性值。

javascript 复制代码
const NavigationContext = createContext(null)
const LocationContext = createContext(null)

function Router({ location, navigator, navigationType, children }) {
  return (
    <NavigationContext.Provider value={{ navigator }}>
      <LocationContext.Provider value={{ location, navigationType }}>
        {children}
      </LocationContext.Provider>
    </NavigationContext.Provider>
  )
}

Link组件负责渲染a标签,当触发点击事件时会调用自定义history.push方法变更页面路由,然后触发组件更新渲染。

javascript 复制代码
function Link({ to, children }) {
  const { navigator } = useContext(NavigationContext)

  return (
    <a
      onClick={() => {
        navigator.push(to)
      }}
    >
      {children}
    </a>
  )
}

2.3 Routes

Routes组件负责解析route配置,并根据当前页面路由匹配需要渲染的route配置。

javascript 复制代码
function useRoutes(routes) {
  const location = useLocation()
  // 获取匹配的路由
  const matches = matchRoutes(routes, location)
  return matches.map((match, index) => (
    <RouteContext.Provider key={index} value={{ match }}>
      {match.route.element}
    </RouteContext.Provider>
  ))
}

// 遍历Route组件创建其对应的route配置
function createRoutesFromChildren(children) {
  return children.map(element => ({
    path: element.props.path,
    element: element.props.element,
  }))
}

function Routes({ children }) {
  return useRoutes(createRoutesFromChildren(children))
}

2.4 Route

Route组件没什么业务逻辑,其作用类似React.Fragment组件,主要是为Routes组件服务的,便于转换成对应的route配置。

javascript 复制代码
function Route({ path, element }) {
  return <></>
}

三. 总结

react-router核心原理是采用路由劫持,当页面路由变更时触发组件更新渲染,然后获取当前页面路由对应的组件进行渲染。代码仓库

创作不易,如果文章对你有帮助,那点个小小的赞吧,你的支持是我持续更新的动力!

相关推荐
遇到困难睡大觉哈哈2 小时前
Harmony os 静态卡片(ArkTS + FormLink)详细介绍
前端·microsoft·harmonyos·鸿蒙
用户47949283569152 小时前
Bun 卖身 Anthropic!尤雨溪神吐槽:OpenAI 你需要工具链吗?
前端·openai·bun
p***43482 小时前
前端在移动端中的网络请求优化
前端
g***B7382 小时前
前端在移动端中的Ionic
前端
拿破轮3 小时前
使用通义灵码解决复杂正则表达式替换字符串的问题.
java·服务器·前端
whltaoin3 小时前
【 Web认证 】Cookie、Session 与 JWT Token:Web 认证机制的原理、实现与对比
前端·web·jwt·cookie·session·认证机制
Aevget3 小时前
界面组件Kendo UI for React 2025 Q3亮点 - AI功能全面提升
人工智能·react.js·ui·界面控件·kendo ui·ui开发
Aerelin3 小时前
爬虫playwright入门讲解
前端·javascript·html·playwright
笙年3 小时前
JavaScript Promise,包括构造函数、对象方法和类方法
开发语言·javascript·ecmascript
桜吹雪3 小时前
LangChain.js/DeepAgents可观测性
javascript·人工智能