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

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

相关推荐
weixin_BYSJ19878 分钟前
「课设设计」springboot校园超市助购系统26449 (附源码)
java·javascript·spring boot·python·django·flask·php
Highcharts.js27 分钟前
如何下载使用Highcharts Grid 开发web可编辑表格
前端·人工智能·grid 表格·web 表格·在线编辑表格·highcharts表格安装·表格开发工具
小月土星35 分钟前
React 状态管理核心原理:从批量 DOM 到派生状态
react.js
用户62960593247051 小时前
从“浏览器根本区分不了”到真正实现:刷新不退出,关闭标签页自动退出,前端判断浏览器关闭和刷新
前端
qetfw1 小时前
MWU:Vue 3 + FastAPI 的 MaaFramework 跨平台 WebUI 源码
前端·vue.js·python·fastapi·开源项目·效率工具
mfxcyh1 小时前
Vue3+Ant Design Vue 实现手动异步文件上传(含大小校验、弹窗上传)
前端·javascript·vue.js
wordbaby1 小时前
Web端热敏标签打印完整解决方案(QZ Tray + 译维A42)
前端
swipe1 小时前
03|Axios 请求进了后端之后:Controller、Request、Response 是怎么接住它的?
前端·后端·全栈
meilindehuzi_a1 小时前
Vue3进阶基础:指令修饰符、v-model表单绑定、动态样式、计算属性与侦听器实战
前端·javascript·vue.js
csdn2015_1 小时前
vue 前端运行命令
前端·javascript·vue.js