閱讀此文章,你能夠了解到:vue
Vue.use
是用來安裝插件的。node
用法:ios
Vue.use(plugin)vue-router
install
方法。會將 Vue 做爲參數傳入
。該方法須要在調用 new Vue()
以前被調用。axios
當 install 方法被同一個插件屢次調用,插件將只會被安裝一次。(源碼解析的時候會解析如何實現)api
總結:Vue.use是官方提供給開發者的一個api,用來註冊、安裝類型Vuex、vue-router、ElementUI之類的插件的。數組
咱們在用Vue-cli3.0裏面初始化項目的時候,會生成一個入口文件main.js
app
在main.js
中,如何咱們安裝了Vue-Router、Vuex、ElementUI,而且想要在項目中使用,就得在入口文件main.js
中調用一下Vue.use()函數
Vue.use(ElementUi);
Vue.use(Vuex);
Vue.use(Router);
複製代碼
這樣就算是完成了對三個插件的安裝,咱們就能夠在組件中調用 this.$router
、this.$route
、this.$store
、this.$alert()
(ElementUI的彈窗組件)參數(方法)。ui
咱們在講什麼是Vue.use的時候,已經說明要用use安裝的插件,要麼是一個對象裏面包含install方法,要麼自己就是一個方法(自身就是install方法)。
也就是說,這個題目的答案,本質就是:Vue-Router、Vuex、ElementUI三者都具備install方法,而且插件的運行依賴於install方法裏的一些操做,才能正常運行,而axios沒有install方法也能正常運行。
看到這裏你必定會疑惑:
在探究這個問題以前,咱們先看看Vue.use這個方法到底作了什麼。
export function initUse (Vue: GlobalAPI) {
Vue.use = function (plugin: Function | Object) {
// 獲取已經安裝的插件
const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
// 看看插件是否已經安裝,若是安裝了直接返回
if (installedPlugins.indexOf(plugin) > -1) {
return this
}
// toArray(arguments, 1)實現的功能就是,獲取Vue.use(plugin,xx,xx)中的其餘參數。
// 好比 Vue.use(plugin,{size:'mini', theme:'black'}),就會回去到plugin意外的參數
const args = toArray(arguments, 1)
// 在參數中第一位插入Vue,從而保證第一個參數是Vue實例
args.unshift(this)
// 插件要麼是一個函數,要麼是一個對象(對象包含install方法)
if (typeof plugin.install === 'function') {
// 調用插件的install方法,並傳入Vue實例
plugin.install.apply(plugin, args)
} else if (typeof plugin === 'function') {
plugin.apply(null, args)
}
// 在已經安裝的插件數組中,放進去
installedPlugins.push(plugin)
return this
}
}
複製代碼
總結:
Vue.use方法主要作了以下的事:
- 檢查插件是否安裝,若是安裝了就再也不安裝
- 若是沒有沒有安裝,那麼調用插件的install方法,並傳入Vue實例
咱們知道了Vue.use作了什麼以後。咱們看看那些咱們常見的插件,是如何利用這個use方法的。
const install = function(Vue, opts = {}) {
locale.use(opts.locale);
locale.i18n(opts.i18n);
// components是ElementUI的組件數組,裏面有Dialog、Input之類的組件
// 往Vue上面掛載組件
components.forEach(component => {
Vue.component(component.name, component);
});
Vue.use(Loading.directive);
// 自定義一些參數
Vue.prototype.$ELEMENT = {
size: opts.size || '',
zIndex: opts.zIndex || 2000
};
// 在Vue原型上註冊一些方法,這就是爲何咱們能夠直接使用this.$alert、this.$loading的緣由,值就是這麼來的。
Vue.prototype.$loading = Loading.service;
Vue.prototype.$msgbox = MessageBox;
Vue.prototype.$alert = MessageBox.alert;
Vue.prototype.$confirm = MessageBox.confirm;
Vue.prototype.$prompt = MessageBox.prompt;
Vue.prototype.$notify = Notification;
Vue.prototype.$message = Message;
};
複製代碼
一樣的方法,咱們來看看Vue-Router的install又作了什麼。
咱們先把這個install方法的部分拆解出來,只關注其最最核心的邏輯
若是不想讀源碼,能夠直接看源碼後面的文字簡單總結
import View from './components/view'
import Link from './components/link'
export let _Vue
export function install (Vue) {
_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)) {
// 設置根組件叫_routerRoot
this._routerRoot = this
// 根組件的_router屬性爲,new Vue傳進去的router
// $options是在mains.js中,new Vue裏的參數,在這裏咱們傳入的參數,
this._router = this.$options.router
this._router.init(this)
// 經過defineReactive方法,來把this._router.history.current變成響應式的,這個方法的底層就是object.defineProperty
Vue.util.defineReactive(this, '_route', this._router.history.current)
} else {
// 若是該組件不是根組件,那麼遞歸往上找,知道找到根組件的。
// 由於Vue渲染組件是先渲染根組件,而後渲染根組件的子組件啊,而後再渲染孫子組件。
// 結果就是每個組件都有this._routerRoot屬性,該屬性指向了根組件。
this._routerRoot = (this.$parent && this.$parent._routerRoot) || this
}
registerInstance(this, this)
},
destroyed () {
registerInstance(this)
}
})
// 把自身$router代理爲this._routerRoot(根組件的)的_router
// 根組件的_router,就是new Vue傳入的 router
// 這樣就實現了,每個Vue組件都有$router、$route屬性
Object.defineProperty(Vue.prototype, '$router', {
get () { return this._routerRoot._router }
})
// 同理,這樣就是把自身的$route,代理到根組件傳入的route
Object.defineProperty(Vue.prototype, '$route', {
get () { return this._routerRoot._route }
})
// 註冊 <router-view>組件
Vue.component('RouterView', View)
// 註冊<router-link>組件
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-router的install方法主要幫咱們作了以下事情:
- 經過minxi混入的方式,若是自身是根組件,就把根組件的_router屬性映射爲new Vue傳入的router實例(this.$options.router)。
- 若是自身不是根組件,那麼層層往上找,直到找到根組件,並用_routerRoot標記出根組件
- 爲每個組件代理
$router
、$route
屬性,這樣每個組件均可以去到$router
、$route
- 註冊
<router-link>
、<router-view>
組件
看到這裏,你應該明白了,爲何vueRouter須要install才能使用了吧。
底層一點的理由就是,vueRouter須要在install方法,對Vue實例作一些自定義化的操做:好比在vue.prototype中添加$router、$route
屬性、註冊<router-link>
組件
其實理由也很簡單,跟上面須要install的相反的。由於axios是基於Promise封裝的庫,是徹底獨立於Vue的,根本不須要掛載在Vue上也能實現發送請求。
而由於VueRouter須要爲咱們提供$router、$routers
之類的屬性,要依賴與Vue或者操做Vue實例才能實現。
Vue.use實際上就是Vue實例與插件的一座橋樑。
我這裏打算分享一下本身之前作的項目裏,把axios改寫成一個相似插件的思路。
要寫其餘插件的思路也類似的。
// api.js
import login from './login'; // login頁面全部的aixos請求封裝在此
import home from './home'; // home頁面的全部請求封裝在此
import detail from './detail'; // 詳細頁面的請求封裝在此
const apiList = {
...login,
...home,
...detail,
};
const install = (Vue) => {
if (install.installed) return;
install.installed = true;
/* 定義屬性到Vue原型中 這樣每個組件就能夠經過this.$api.xxx(data) 去發送請求 */
Object.defineProperties(Vue.prototype, {
$api: {
get() {
return apiList;
},
},
});
};
// 導出一個對象,裏面有install方法。install方法裏就把$api代理到Vue中
export default {
install,
};
複製代碼
而後在mains.j
s中,就能夠這樣寫了
import apis from './apis';
Vue.use(apis);
new Vue(參數);
複製代碼