路由注册
Vue 从它的设计上就是一个渐进式 JavaScript 框架,它本身的核心是解决视图渲染的问题,其它的能力就通过插件的方式来解决。Vue-Router 就是官方维护的路由插件,在介绍它的注册实现之前,我们先来分析一下 Vue 通用的插件注册原理。
Vue.use
Vue 提供了 Vue.use
的全局 API 来注册这些插件,所以我们先来分析一下它的实现原理,定义在 vue/src/core/global-api/use.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
了。
路由安装
Vue-Router 的入口文件是 src/index.js
,其中定义了 VueRouter
类,也实现了 install
的静态方法:VueRouter.install = install
,它的定义在 src/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)
},
destroyed () {
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
的定义,在 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
}
}
它的实现实际上非常简单,就是把要混入的对象通过 mergeOptions
合并到 Vue
的 options
中,由于每个组件的构造函数都会在 extend
阶段合并 Vue.options
到自身的 options
中,所以也就相当于每个组件都定义了 mixin
定义的选项。
回到 Vue-Router
的 install
方法,先看混入的 beforeCreate
钩子函数,对于根 Vue
实例而言,执行该钩子函数时定义了 this._routerRoot
表示它自身;this._router
表示 VueRouter
的实例 router
,它是在 new Vue
的时候传入的;另外执行了 this._router.init()
方法初始化 router
,这个逻辑之后介绍,然后用 defineReactive
方法把 this._route
变成响应式对象,这个作用我们之后会介绍。而对于子组件而言,由于组件是树状结构,在遍历组件树的过程中,它们在执行该钩子函数的时候 this._routerRoot
始终指向的离它最近的传入了 router
对象作为配置而实例化的父实例。
对于 beforeCreate
和 destroyed
钩子函数,它们都会执行 registerInstance
方法,这个方法的作用我们也是之后会介绍。
接着给 Vue 原型上定义了 $router
和 $route
2 个属性的 get 方法,这就是为什么我们可以在组件实例上可以访问 this.$router
以及 this.$route
,它们的作用之后介绍。
接着又通过 Vue.component
方法定义了全局的 <router-link>
和 <router-view>
2 个组件,这也是为什么我们在写模板的时候可以使用这两个标签,它们的作用也是之后介绍。
最后定义了路由中的钩子函数的合并策略,和普通的钩子函数一样。
总结
那么到此为止,我们分析了 Vue-Router 的安装过程,Vue 编写插件的时候通常要提供静态的 install
方法,我们通过 Vue.use(plugin)
时候,就是在执行 install
方法。Vue-Router
的 install
方法会给每一个组件注入 beforeCreate
和 destoryed
钩子函数,在 beforeCreate
做一些私有属性定义和路由初始化工作,下一节我们就来分析一下 VueRouter
对象的实现和它的初始化工作。