Vue 从它的设计上就是一个渐进式 JavaScript 框架,它本身的核心是解决视图渲染的问题,其它的能力就通过插件的方式来解决。Vue-Router 就是官方维护的路由插件
Vue.use
Vue 提供了 Vue.use 的全局 API 来注册这些插件
分析Vue.use的实现原理,定义在 vue/src/core/global-api/use.js 中
export function initUse (Vue: GlobalAPI) {// 传入plugin参数Vue.use = function (plugin: Function | Object) {// _installedPlugins维护的数组,存储所有注册过的pluginconst installedPlugins = (this._installedPlugins || (this._installedPlugins = []))if (installedPlugins.indexOf(plugin) > -1) {return this}// additional parametersconst args = toArray(arguments, 1)args.unshift(this)// 判断plugin有没有定义install方法if (typeof plugin.install === 'function') {// 调用install方法,并且该方法执行的第一个参数是Vueplugin.install.apply(plugin, args)} else if (typeof plugin === 'function') {plugin.apply(null, args)}// 把plugin储存到installedPlugins中installedPlugins.push(plugin)return this}}
可以看到 Vue 提供的插件注册机制很简单,每个插件都需要实现一个静态的 install 方法,当执行 Vue.use 注册插件的时候,就会执行这个 install 方法,并且在这个 install 方法的第一个参数我们可以拿到 Vue 对象,这样的好处就是作为插件的编写方不需要再额外去import Vue 了
路由安装
Vue-Router 的入口文件是 src/index.js,其中定义了 VueRouter 类,也实现了 install 的静态方法:VueRouter.install = install,它的定义在 src/install.js 中
export let _Vueexport function install (Vue) {// installed标志位if (install.installed && _Vue === Vue) returninstall.installed = true// 全局_Vue来接收参数Vue// 因为作为Vue插件对Vue对象是有依赖的,但又不能去单独去import Vue,因为那样会增加包体积_Vue = Vueconst isDef = v => v !== undefinedconst registerInstance = (vm, callVal) => {let i = vm.$options._parentVnodeif (isDef(i) && isDef(i = i.data) && isDef(i = i.registerRouteInstance)) {i(vm, callVal)}}// 利用Vue.mixin去把beforeCreate和destoryed钩子函数注入到每一个组件中Vue.mixin({beforeCreate () {if (isDef(this.$options.router)) {// 对于根Vue实例而言// this._routerRoot表示它自身this._routerRoot = this// this._router表示VueRouter的实例router,是在new Vue的时候注入的this._router = this.$options.router// 初始化routerthis._router.init(this)// 把this._route变成响应式对象Vue.util.defineReactive(this, '_route', this._router.history.current)} else {this._routerRoot = (this.$parent && this.$parent._routerRoot) || this}registerInstance(this, this)// 对于子组件而言,由于组件是树状结构,在遍历组件树的过程中,它们在执行该钩子函数的时候 this._routerRoot 始终指向的离它最近的传入了 router 对象作为配置而实例化的父实例},destroyed () {registerInstance(this)}})// 给Vue原型上定义了$router和$route这两个属性的get方法// 这就是为什么可以在组件实例上可以访问 this.$router 以及 this.$routeObject.defineProperty(Vue.prototype, '$router', {get () { return this._routerRoot._router }})Object.defineProperty(Vue.prototype, '$route', {get () { return this._routerRoot._route }})// 定义全局的<router-link>和<router-view>2个组件Vue.component('RouterView', View)Vue.component('RouterLink', Link)const strats = Vue.config.optionMergeStrategies// use the same hook merging strategy for route hooks// 定义了路由中的钩子函数和合并策略,和普通钩子函数一样strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate = strats.created}
当用户执行Vue.use(VueRouter)时实际上就是在执行install函数
为了确保 install 逻辑只执行一次,用了 install.installed 变量做已安装的标志位
Vue.mixin 定义在 vue/src/core/global-api/mixin.js 中
export function initMixin (Vue: GlobalAPI) {Vue.mixin = function (mixin: Object) {// 把要混入的对象通过mergeOptions合并到Vue的options中this.options = mergeOptions(this.options, mixin)return this}}
由于每个组件的构造函数都会在 extend 阶段合并 Vue.options 到自身的 options 中,所以也就相当于每个组件都定义了 mixin 定义的选项
Vue 编写插件的时候通常要提供静态的 install 方法,我们通过 Vue.use(plugin) 时候,就是在执行 install 方法Vue-Router 的 install 方法会给每一个组件注入 beforeCreate 和 destoryed 钩子函数,在 beforeCreate 做一些私有属性定义和路由初始化工作
