Router

Router 对象是通过 createRouter() 创建并返回的对象,它定在 router.ts 中:

const router: Router = {
  // 当前路由currentRoute
  currentRoute,

  // 允许关闭历史事件的监听器。这是一个为微前端提供的底层 API。
  listening: true,

  // 添加一个新的路由记录,将其作为一个已有路由的子路由。
  addRoute,
  // 根据其名称移除一个现有的路由。
  removeRoute,
  // 检查一个给定名称的路由是否存在。
  hasRoute,
  // 获得所有路由记录的完整列表。
  getRoutes,
  // 返回一个路由地址的规范化版本。同时包含一个包含任何现有 base 的 href 属性。
  // 默认情况下,用于 router.currentRoute 的 currentLocation 应该在特别高阶的用例下才会被覆写。
  resolve,
  // 创建路由器时的原始选项对象。
  options,

  // 程序式地通过将一条记录加入到历史栈中来导航到一个新的 URL。
  push,
  // 程序式地通过替换历史栈中的当前记录来导航到一个新的 URL。
  replace,
  // 允许你在历史中前进或后退。相当于 router.go()。
  go,
  // 通过调用 history.back() 在可能的情况下在历史中后退。相当于 router.go(-1)。
  back: () => go(-1),
  // 通过调用 history.forward() 在可能的情况下在历史中前进。相当于 router.go(1)。
  forward: () => go(1),

  // 路由守卫的方法beforeEach、beforeResolve、afterEach。
  beforeEach: beforeGuards.add,
  beforeResolve: beforeResolveGuards.add,
  afterEach: afterGuards.add,

  /**
   * 添加一个错误处理器,它会在每次导航遇到未被捕获的错误出现时被调用。
   * 其中包括同步和异步被抛出的错误、在任何导航守卫中返回或传入 next 的错误、尝试解析一个需要渲染路由的异步组件时发生的错误。
   */
  onError: errorListeners.add,
  /**
   * 返回一个 Promise,它会在路由器完成初始导航之后被解析,也就是说这时所有和初始路由有关联的异步入口钩子和异步组件都已经被解析。
   * 如果初始导航已经发生,则该 Promise 会被立刻解析。这在服务端渲染中确认服务端和客户端输出一致的时候非常有用。
   * 注意在服务端你需要手动加入初始地址,而在客户端,路由器会从 URL 中自动获取。
   */
  isReady,

  // 用于插件化安装
  install(app: App) {
    const router = this
    // 注册两个全局组件:RouterView和RouterLink。
    app.component('RouterLink', RouterLink)
    app.component('RouterView', RouterView)

    // 在app实例上注入了$router和$route
    app.config.globalProperties.$router = router
    Object.defineProperty(app.config.globalProperties, '$route', {
      enumerable: true,
      get: () => unref(currentRoute),
    })
    // 检查客户端环境下的初始导航逻辑
    if (isBrowser && !started && currentRoute.value === START_LOCATION_NORMALIZED) {
      started = true
      push(routerHistory.location).catch(err => {
        if (__DEV__) warn('Unexpected error when starting the router:', err)
      })
    }

    // 提供响应式的路由location对象
    const reactiveRoute = {} as RouteLocationNormalizedLoaded
    for (const key in START_LOCATION_NORMALIZED) {
      Object.defineProperty(reactiveRoute, key, {
        get: () => currentRoute.value[key as keyof RouteLocationNormalized],
        enumerable: true,
      })
    }

    // 使用app.provide注入router实例
    app.provide(routerKey, router)
    app.provide(routeLocationKey, shallowReactive(reactiveRoute))
    app.provide(routerViewLocationKey, currentRoute)

    const unmountApp = app.unmount
    installedApps.add(app)
    // 注册unmount钩子函数
    app.unmount = function () {
      installedApps.delete(app)
      if (installedApps.size < 1) {
        pendingLocation = START_LOCATION_NORMALIZED
        removeHistoryListener && removeHistoryListener()
        removeHistoryListener = null
        currentRoute.value = START_LOCATION_NORMALIZED
        started = false
        ready = false
      }
      unmountApp()
    }
    // 在开发环境下添加devtools支持
    if ((__DEV__ || __FEATURE_PROD_DEVTOOLS__) && isBrowser) {
      addDevtools(app, router, matcher)
    }
  }
}

createRouter函数的主要逻辑:

  • 创建路由匹配器matcher,用于后续匹配路由配置。
  • 定义解析查询参数和序列化查询参数的方法parseQuery和stringifyQuery。
  • 接收history实现对象routerHistory。
  • 定义路由导航守卫beforeGuards、beforeResolveGuards、afterGuards。
  • 创建当前路由响应式ref对象currentRoute。
  • 定义路由操作方法push、replace等。
  • 创建路由实例,并返回:
    • 返回当前路由currentRoute。
    • 返回路由操作方法push、replace等。
    • 返回添加路由守卫的方法beforeEach、beforeResolve、afterEach。
    • 返回路由就绪方法isReady。
    • 返回安装方法install,用于插件化安装。
  • 在创建时不会解析路由配置,这是在安装时才会做的。
  • 创建时也不会触发初始导航,这是在安装后才会做的。