注册

Vue 从它的设计上就是一个渐进式 JavaScript 框架,它本身的核心是解决视图渲染的问题,其它的能力就通过插件的方式来解决。Vue-Router 就是官方维护的路由插件,在介绍它的注册实现之前,我们先来分析一下 Vue3 通用的插件注册原理。

插件

插件 (Plugins) 是一种能为 Vue 添加全局功能的工具代码。下面是如何安装 vue-router 的示例:

import { createApp } from 'vue'
import { createRouter, createWebHistory } from 'vue-router'

const router = createRouter({
  routes: [],
  history: createWebHistory('/'),
  scrollBehavior: () => ({ top: 0 })
})

const app = createApp(App)

app.use(router)

一个插件可以是一个拥有 install() 方法的对象,也可以直接是一个安装函数本身。安装函数会接收到安装它的应用实例和传递给 app.use() 的额外选项作为参数:

const customPlugin = {
  install(app, options) {
    // 配置此应用
  }
}

插件没有严格定义的使用范围,但是插件发挥作用的常见场景主要包括以下几种:

  • 通过 app.component()app.directive() 注册一到多个全局组件或自定义指令。
  • 通过 app.provide() 使一个资源可被注入进整个应用。
  • app.config.globalProperties 中添加一些全局实例属性或方法
  • 一个可能上述三种都包含了的功能库 (例如 vue-router)。

app.use()

Vue3 提供了实例方法 use() 来安装一个插件,定义在 apiCreateApp.ts 中:

const app: App = (context.app = {
  use(plugin: Plugin, ...options: any[]) {
    // installedPlugins Set 类型存储已经安装的插件
    if (installedPlugins.has(plugin)) {
      __DEV__ && warn(`Plugin has already been applied to target app.`)
    } else if (plugin && isFunction(plugin.install)) {
      // plugin && isFunction(plugin.install) 拥有 install() 方法的对象
      installedPlugins.add(plugin)
      // 执行install方法,将app实例作为第一个参数
      plugin.install(app, ...options)
    } else if (isFunction(plugin)) {
      installedPlugins.add(plugin)
      // 插件本身就是一个函数,将app实例作为第一个参数
      plugin(app, ...options)
    } else if (__DEV__) {
      warn(
        `A plugin must either be a function or an object with an "install" ` +
        `function.`
      )
    }
    return app
  }
})

app.use() 接受一个 plugin 参数和一个 options 参数,并且维护一个 installedPlugins Set,用来存储已经注册的plugin;注册流程如下:

  • 判断是否 plugin 是否已经注册,开发环境下对已经注册的插件再次注册,会打印警告 Plugin has already been applied to target app.
  • 判断 plugin 是否定义 install 函数,有则将插件添加到 installedPlugins Set集合中,调用 install 方法,并将 app 实例作为第一个参数
  • 不存在 install 方法,判断 plugin 自身是否是一个函数,是则将插件添加到 installedPlugins Set集合中,将 app 实例作为第一个参数调用该函数
  • 以上条件都不是,在开发环境下,打印警告 A plugin must either be a function or an object with an "install" function.

路由注册

在 Vue Router 4 中,router 对象提供了 install 方法,这是一个插件安装方法,用于将 vue-router 安装到 Vue 应用程序中。定义在 router.ts 中:

  const router: Router = {

  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)
    }
  }
}

总结

上面我们分析了,Vue 插件的注册机制,Vue 插件编写时通常提供 install 方法,通过 app.use(plugin) 的时候,就会执行 install 方法。Vue-Router,在通过 createRouter() 方法,创建 router 实例时,会返回一个包含 install 方法的对象。 router 中 install 方法中主要做了以下工作:

  • 注册了RouterLink和RouterView组件
  • 在app实例上注入了$router和$route
  • 检查客户端环境下的初始导航逻辑
  • 提供响应式的路由location对象
  • 使用app.provide注入router实例
  • 注册unmount钩子函数
  • 在开发环境下添加devtools支持