vue-router源码阅读-1.从createRouter开始

245 阅读2分钟

前言

本文是vue-router源码阅读系列的第一章, 主要介绍了vue-router的源码入口概览. 系列其他文章如下:

vue-router的使用


// ts文件中
import { createRouter } from "vue-router";
const router = createRouter({});
createApp(App).use(router);

// vue文件中
import { useRouter } from "vue-router";
const router = useRouter();

从上面代码中可以看到, 使用vue-router需要先用#createRouter创建一个router实例, 然后将其作为插件使用. 而在vue文件中使用router需要调用#useRouter来获取一个router实例.

从 createRouter 开始


先从createRouter()开始来学习vue-router的具体实现.

export function createRouter(options: RouterOptions): Router 

上面是#createRouter的函数签名, 该函数接收一个RouterOptions配置参数对象, 返回一个Router对象, 也就是供vue使用的router插件.

Router的接口如下:

export interface Router {
  readonly currentRoute: Ref<RouteLocationNormalizedLoaded>;
  readonly options: RouterOptions;
  listening: boolean;
  addRoute(parentName: RouteRecordName, route: RouteRecordRaw): () => void;
  addRoute(route: RouteRecordRaw): () => void;
  removeRoute(name: RouteRecordName): void;
  hasRoute(name: RouteRecordName): boolean;
  getRoutes(): RouteRecord[];
  resolve(
    to: RouteLocationRaw,
    currentLocation?: RouteLocationNormalizedLoaded
  ): RouteLocation & { href: string };
  push(to: RouteLocationRaw): Promise<NavigationFailure | void | undefined>;
  replace(to: RouteLocationRaw): Promise<NavigationFailure | void | undefined>;
  back(): ReturnType<Router["go"]>;
  forward(): ReturnType<Router["go"]>;
  go(delta: number): void;
  beforeEach(guard: NavigationGuardWithThis<undefined>): () => void;
  beforeResolve(guard: NavigationGuardWithThis<undefined>): () => void;
  afterEach(guard: NavigationHookAfter): () => void;
  onError(handler: _ErrorHandler): () => void;
  isReady(): Promise<void>;
  install(app: App): void;
}

可以看到, router是一个拥有#install的对象, 可作为插件使用. 接下来就从#createRouter开始, 查看其是如何返回一个router对象的.

下面是#createRouter的返回部分代码, 其创建了一个Router, 将在函数体内部实现的方法赋值到了Router的同名属性中, 可以看到整个router的内容可分为六大类:

  1. 属性
  2. route相关方法, 该部分方法主要用于操作router中的route
  3. history相关方法, 该部分方法用于页面的跳转
  4. 路由守卫
  5. 事件
  6. install方法
  const router: Router = {
    currentRoute, // 当前路由位置, 默认值为 START_LOCATION_NORMALIZED, 定义在 '@/types/index.ts'
    listening: true,  // 监听历史事件

    // route相关方法
    addRoute, 
    removeRoute,
    hasRoute,
    getRoutes,
    resolve,
    options,

    // history相关方法
    push,
    replace,
    go,
    back: () => go(-1),
    forward: () => go(1),

    // 路由守卫
    beforeEach: beforeGuards.add,
    beforeResolve: beforeResolveGuards.add,
    afterEach: afterGuards.add,

    // 事件监听器
    onError: errorHandlers.add,
    isReady,

    // 在 app.use(router) 执行时被自动调用, 
    // 在客户端调用时触发初始化导航
    install(app: App) {
      const router = this

      // 添加路由组件 RouterLink 和 RouterView
      app.component('RouterLink', RouterLink)
      app.component('RouterView', RouterView)


      // 添加全局属性 $router
      app.config.globalProperties.$router = router
      Object.defineProperty(app.config.globalProperties, '$route', {
        enumerable: true,
        get: () => unref(currentRoute),
      })

      // 初始化导航, 仅在环境是客户端浏览器时执行, 在服务器端执行可能会导致错误
      if (
        isBrowser &&
        // 避免在拥有多个 app 实例时多次进行初始化导航
        !started &&
        currentRoute.value === START_LOCATION_NORMALIZED
      ) {
        started = true
        push(routerHistory.location).catch(err => {
          if (__DEV__) warn('Unexpected error when starting the router:', err)
        })
      }

      // 遍历 START_LOCATION_NORMALIZED 对象, 将其键复制到 reactiveRouter 中
      // 并将其变为计算属性便于追踪
      const reactiveRoute = {} as {
        [k in keyof RouteLocationNormalizedLoaded]: ComputedRef<
          RouteLocationNormalizedLoaded[k]
        >
      }
      for (const key in START_LOCATION_NORMALIZED) {
        // @ts-expect-error: the key matches
        reactiveRoute[key] = computed(() => currentRoute.value[key])
      }

      // 将 router, reactiveRoute, currentRoute 暴露给所有组件, 键为 symbol
      // 因为 <setup> 中无法访问 this, 因此再次使用 provide 暴露
      app.provide(routerKey, router)
      app.provide(routeLocationKey, reactive(reactiveRoute))
      app.provide(routerViewLocationKey, currentRoute)

      const unmountApp = app.unmount
      // 将 app 加入到已加载的 Apps 集合中保存
      installedApps.add(app)

      // 设定 app 注销时的行为
      app.unmount = function () {
        // 将 app 从集合中移除
        installedApps.delete(app)
        // 不存在任何 app 使用 router 时将 router 重置
        if (installedApps.size < 1) {
          pendingLocation = START_LOCATION_NORMALIZED
          removeHistoryListener && removeHistoryListener()
          removeHistoryListener = null
          currentRoute.value = START_LOCATION_NORMALIZED
          started = false
          ready = false
        }
        // 注销 app
        unmountApp()
      }

      if ((__DEV__ || __FEATURE_PROD_DEVTOOLS__) && isBrowser) {
        addDevtools(app, router, matcher)
      }
    },
  }

  return router

#install中主要实现了以下功能

  • 添加两个路由组件router-link和router-view
  • 添加vue全局属性$route, 值为当前页面路由
  • 使用provide暴露router, currentRoute和reactiveRoute
  • 设定app注销时行为