Vue.use(plugin)詳解

Edon-發表於2019-09-20

前言

閱讀此文章,你可以瞭解到:

  • Vue.use(plugin)基礎概念(什麼是Vue.use(plugin))
  • Vue.use的簡單使用
  • 為什麼在引入Vue-Router、ElementUI的時候需要Vue.use()?而引入axios的時候,不需要Vue.use()?
  • Vue-Router、ElementUI在Vue.use()分別做了什麼?
  • Vue.use原理
  • 如何編寫一個Vue外掛?

什麼是Vue.use(plugin)

Vue.use是用來安裝外掛的。

用法

Vue.use(plugin)

  • 如果外掛是一個物件,必須提供 install 方法。
  • 如果外掛是一個函式,它會被作為 install 方法。install 方法呼叫時,會將 Vue 作為引數傳入
  • Vue.use(plugin)呼叫之後,外掛的install方法就會預設接受到一個引數,這個引數就是Vue(原理部分會將)

該方法需要在呼叫 new Vue() 之前被呼叫。

當 install 方法被同一個外掛多次呼叫,外掛將只會被安裝一次。(原始碼解析的時候會解析如何實現)

總結:Vue.use是官方提供給開發者的一個api,用來註冊、安裝型別Vuex、vue-router、ElementUI之類的外掛的。


Vue.use的簡單使用

看一下具體的例子:

我們在用Vue-cli3.0裡面初始化專案的時候,會生成一個入口檔案main.js

main.js中,如何我們安裝了Vue-Router、Vuex、ElementUI,並且想要在專案中使用,就得在入口檔案main.js中呼叫一下Vue.use()

Vue.use(ElementUi);
Vue.use(Vuex);
Vue.use(Router);
複製程式碼

這樣就算是完成了對三個外掛的安裝,我們就可以在元件中呼叫 this.$routerthis.$routethis.$storethis.$alert()(ElementUI的彈窗元件)引數(方法)。


為什麼在引入Vue-Router、Vuex、ElementUI的時候需要Vue.use()?而引入axios的時候,不需要Vue.use()?

我們在講什麼是Vue.use的時候,已經說明要用use安裝的外掛,要麼是一個物件裡面包含install方法,要麼本身就是一個方法(自身就是install方法)。

也就是說,這個題目的答案,本質就是:Vue-Router、Vuex、ElementUI三者都具有install方法,並且外掛的執行依賴於install方法裡的一些操作,才能正常執行,而axios沒有install方法也能正常執行。

看到這裡你一定會疑惑:

  • 同樣是外掛,為什麼有些外掛要有install方法才能正常執行(如VueRouter),有一些卻可以沒有install方法也可以使用(如axios)?
  • 外掛的install方法,可以為我們做什麼?

Vue-Router、ElementUI在install裡面到底做了什麼?

在探究這個問題之前,我們先看看Vue.use這個方法到底做了什麼。

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方法主要做了如下的事:

  1. 檢查外掛是否安裝,如果安裝了就不再安裝
  2. 如果沒有沒有安裝,那麼呼叫外掛的install方法,並傳入Vue例項

我們知道了Vue.use做了什麼之後。我們看看那些我們常見的外掛,是如何利用這個use方法的。

Element中的install

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又做了什麼。

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方法主要幫我們做了如下事情:

  1. 通過minxi混入的方式,如果自身是根元件,就把根元件的_router屬性對映為new Vue傳入的router例項(this.$options.router)。
  2. 如果自身不是根元件,那麼層層往上找,直到找到根元件,並用_routerRoot標記出根元件
  3. 為每一個元件代理$router$route屬性,這樣每一個元件都可以去到$router$route
  4. 註冊<router-link><router-view>元件

看到這裡,你應該明白了,為什麼vueRouter需要install才能使用了吧。

底層一點的理由就是,vueRouter需要在install方法,對Vue例項做一些自定義化的操作:比如在vue.prototype中新增$router、$route屬性、註冊<router-link>元件

為什麼axios不需要安裝,可以開箱即用?

其實理由也很簡單,跟上面需要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.js中,就可以這樣寫了

import apis from './apis';
Vue.use(apis);
new Vue(引數);

複製程式碼

相關文章