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

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

相关推荐
古时的风筝4 分钟前
暴论:2025年,程序员必学技能就是MCP
前端·后端·mcp
古时的风筝5 分钟前
这编程圈子变化太快了,谁能告诉我 MCP 是什么
前端·后端·mcp
王月lydia10 分钟前
环境变量篇-vue3的H5项目从0到1工程化落地经验篇2
前端
赵要上天10 分钟前
利用TTP协议 ETag + 路由守卫 实现前端发版后通知用户更新得一个方案
前端
李剑一12 分钟前
写一个vitepress新建文章脚本,自动化创建链接,别再手写了!
前端·node.js·vitepress
火星思想12 分钟前
你来说说JavaScript作用域
javascript·ecmascript 6
火星思想12 分钟前
React如何实现时间切片
前端·react.js
小学生豆豆17 分钟前
eslint以及其扩展插件
前端
Electrolux24 分钟前
【前端bug】Safari的选区机制导致的前端@人组件的bug
前端
w236173460126 分钟前
Tomcat:从零理解Java Web应用的“心脏”
java·前端·tomcat