vue-router 插件方式的實現
vue-router 是作爲插件集成到 vue 中的。
我們使用 vue-router 的時候,第一部就是要 安裝插件 Vue.use(VueRouter);
關於插件的介紹可以查看 vue 的官方文檔
我們重點關注如何開發插件
如何開發插件
Vue.js
要求插件應該有一個公開方法 install
。這個方法的第一個參數是 Vue
構造器,第二個參數是一個可選的選項對象。
在 install
方法裏面,便可以做相關的處理:
- 添加全局方法或者屬性
- 添加全局資源:指令/過濾器/過渡等,
- 通過全局 mixin 方法添加一些組件選項,
- 添加 Vue 實例方法,通過把它們添加到
Vue.prototype
上實現。 - 一個庫,提供自己的 API,同時提供上面提到的一個或多個功能
MyPlugin.install = function (Vue, options) {
// 1. 添加全局方法或屬性
Vue.myGlobalMethod = function () {
// 邏輯...
}
// 2. 添加全局資源
Vue.directive('my-directive', {
bind (el, binding, vnode, oldVnode) {
// 邏輯...
}
...
})
// 3. 注入組件
Vue.mixin({
created: function () {
// 邏輯...
}
...
})
// 4. 添加實例方法
Vue.prototype.$myMethod = function (methodOptions) {
// 邏輯...
}
}
在粗略瞭解了 vue.js
插件的實現思路之後,我們來看看 vue-router
的處理
vue-router 的 install
首先查看入口文件 src/index.js
import { install } from './install';
// ...more
VueRouter.install = install;
所以,具體的實現在 install
裏面。接下來我們來看具體做了些什麼 ?
install 實現
install 相對來說邏輯較爲簡單。主要做了以下幾個部分 :
防止重複安裝
通過一個全局變量來確保只安裝一次
// 插件安裝方法
export let _Vue;
export function install(Vue) {
// 防止重複安裝
if (install.installed && _Vue === Vue) return;
install.installed = true;
// ...more
}
通過全局 mixin
注入一些生命週期的處理
export function install(Vue) {
// ...more
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)) {
// 如果 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);
}
});
// ...more
}
我們看到 , 利用mixin
,我們往實例增加了 beforeCreate
以及 destroyed
。在裏面註冊以及銷燬實例。
值得注意的是 registerInstance
函數裏的
vm.$options._parentVnode.data.registerRouteInstance;
你可能會疑惑 , 它是從哪裏來的 。
它是在 ./src/components/view.js
, route-view
組件的 render 方法裏面定義的。主要用於註冊及銷燬實例,具體的我們後期再講~
掛載變量到原型上
通過以下形式,定義變量。我們經常使用到的 this.$router ,this.$route
就是在這裏定義的。
// 掛載變量到原型上
Object.defineProperty(Vue.prototype, '$router', {
get() {
return this._routerRoot._router;
}
});
// 掛載變量到原型上
Object.defineProperty(Vue.prototype, '$route', {
get() {
return this._routerRoot._route;
}
});
這裏通過Object.defineProperty
定義get
來實現 , 而不使用Vue.prototype.$router = this.this._routerRoot._router
。
是爲了讓其只讀,不可修改
註冊全局組件
import View from './components/view';
import Link from './components/link';
export function install(Vue) {
// ...more
// 註冊全局組件
Vue.component('RouterView', View);
Vue.component('RouterLink', Link);
// ...more
}
最後
附上 install.js
完整的代碼
import View from './components/view';
import Link from './components/link';
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)) {
// 如果 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;
// use the same hook merging strategy for route hooks
strats.beforeRouteEnter = strats.beforeRouteLeave = strats.beforeRouteUpdate =
strats.created;
}