手写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核心原理是采用路由劫持,当页面路由变更时触发组件更新渲染,然后获取当前页面路由对应的组件进行渲染。代码仓库

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

相关推荐
CodeCraft Studio几秒前
【案例分享】如何借助JS UI组件库DHTMLX Suite构建高效物联网IIoT平台
javascript·物联网·ui
打小就很皮...32 分钟前
HBuilder 发行Android(apk包)全流程指南
前端·javascript·微信小程序
集成显卡2 小时前
PlayWright | 初识微软出品的 WEB 应用自动化测试框架
前端·chrome·测试工具·microsoft·自动化·edge浏览器
前端小趴菜052 小时前
React - 组件通信
前端·react.js·前端框架
Amy_cx3 小时前
在表单输入框按回车页面刷新的问题
前端·elementui
dancing9993 小时前
cocos3.X的oops框架oops-plugin-excel-to-json改进兼容多表单导出功能
前端·javascript·typescript·游戏程序
HarderCoder3 小时前
学习React的一些知识
react.js
后海 0_o3 小时前
2025前端微服务 - 无界 的实战应用
前端·微服务·架构
Scabbards_3 小时前
CPT304-2425-S2-Software Engineering II
前端
小满zs3 小时前
Zustand 第二章(状态处理)
前端·react.js