2路由注册
- 了解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 了
路由 install
vue-router的入口文件是 src/index.js 其中定义了VueRouter类,也实现了install静态方法
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的定义
// 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方法会给每个组件注入一个beforeCreated和destroyed钩子函数,在beforeCreated做一些私有属性定义和路由初始化工作
