Skip to content

2路由注册

  • 了解Vue插件的注册原理
  • 了解路由注册的实现流程

Vue.use

Vue提供了Vue.use全局API来注册插件,所以我们先来分析它的实现原理,定义在 vue/src/core/global-api/use.js

js
export function initUse(Vue: GlobalAPI) {
  Vue.use = function(plugin: Function | Object) {
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    const args = toArray(arguments, 1)
    args.unshift(this)
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}

vue.use 接受一个plugin参数,并且维护了一个 _installedPlugins 数组,它存储所有注册过的plugin,接着又会判断plugin有没有定义install方法,如果有就调用,且该方法执行第一个参数是Vue;最后把plugin存储到installedPlugins中

Vue的插件机制,即每个插件都需要实现一个静态的install方法,当我们执行 Vue.use 注册时,就会执行install方法,且在install方法的第一个参数拿到Vue对象,这样的好处是作为插件的编写不需要再额外 import Vue 了

路由 install

vue-router的入口文件是 src/index.js 其中定义了VueRouter类,也实现了install静态方法

js
export let _Vue
export function install(Vue) {
  if (install.installed && _Vue === Vue) return
  install.installed = true

  _Vue = Vue

  const isDef = v => v !== undefined
  const registerInstance = (vm, callVal) => {
    let i = vm.$options._parentVnode
    if (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {
      i(vm, callVal)
    }
  }

  Vue.mixin({
    beforeCreate() {
      if (isDef(this.$options.router)) {
        this._routerRoot = this
        this._router = this.$options.router
        this._router.init(this)
        Vue.util.defineReactive(this, '_route', this._router.history.current)
      } else {
        this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
      }
      registerInstance(this, this)
    },
    destoryed() {
      registerInstance(this)
    }
  })

  Object.defineProperty(Vue.prototype, '$router', {
    get() { return this._routerRoot._router }
  })

  Object.defineProperty(Vue.prototype, '$route', {
    get() { return this._routerRoot._route }
  })

  Vue.component('RouterView', View)
  Vue.component('RouterLink', Link)

  const strats = Vue.config.optionMergeStrategies
  strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created
}

当用户执行Vue.use(VueRouter)时,实际上就是执行install函数,为确保install逻辑只执行一次,用了install.installed变量做已安装的标志位。另外一个全局的_Vue来接受参数Vue,因为作为Vue的插件对Vue对象是有依赖的,但又不能单独去import Vue,因为那样会增加包体积,所以就通过这种方式拿到Vue对象

Vue-Router安装最重要一步是利用Vue.mixin去把beforeCreate和destroyed钩子注入到每一个组件中。下面是Vue.mixin的定义

js
// vue/src/core/global-api/mixin.js
export function initMixin(Vue: GlobalAPI) {
  Vue.mixin = function(mixin: Object) {
    this.options = mergeOptions(this.options, mixin)
    return this
  }
}

把要混入对象通过mergeOption合并到Vue的options中,由于每个组件的构造函数都会在extend阶段合并Vue.options到自身的options中,所以也就相当于每个组件都定义了mixin定义的选项。

回到Vue-Router的install方法。对于根Vue实例而言,执行该钩子函数时定义了 this._routerRoot表示它自身;this._router表示VueRouter实例router,它是在new Vue时传入的;另外执行了this._router.init()方法初始化router。然后用defineReactive方法把this._route变成响应式对象。而对于子组件而言,由于组件是树状结构,在遍历组件树过程中,它们在执行该钩子函数时this._routerRoot始终指向的是根Vue实例。

对于beforeCreated和destroyed钩子函数,它们都会执行registerInstance方法。

接着给Vue原型上定义了$router$route两个属性的get方法,这就是为什么我们可以在组件实例上访问this.$router以及this.$route

接着又通过 Vue.component 方法定义了全局 <router-link><router-view> 组件,这也是为什么我们在写模板时可以使用这两个标签

最后定义了路由中钩子函数的合并策略,和普通的钩子函数一样。

小结

我们分析了Vue-Router的安装过程,Vue编写插件时要提供静态install方法,我们通过Vue.use(plugin)时,就是在执行install方法。

Vue-Router的install方法会给每个组件注入一个beforeCreateddestroyed钩子函数,在beforeCreated做一些私有属性定义和路由初始化工作

共 20 个模块,1301 篇 Markdown 文档。