Vue.use源码及Vue插件开发

637 阅读3分钟

其实在 Vue 文档已经对插件的使用和功能有详细的说明,这边也就重复再稍微补充实例。

插件的作用

插件通常用来为 Vue 添加全局功能。插件的功能范围没有严格的限制——一般有下面几种:

  1. 添加全局方法或者 property。如:vue-custom-element

  2. 添加全局资源:指令/过滤器/过渡等。如 vue-touch

  3. 通过全局混入来添加一些组件选项。如 vue-router

  4. 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。

  5. 一个库,提供自己的 API,同时提供上面提到的一个或多个功能。如 vue-router

可以发现,插件的作用主要是帮我们实现一些 vue 的 “全局功能”,比如全局指令,过滤器,组件,注入到原型对象(prototype)的方法或属性。

插件的使用

通过全局方法 Vue.use() 使用插件。它需要在你调用 new Vue() 启动应用之前完成:

// 调用 `MyPlugin.install(Vue)`
Vue.use(MyPlugin)

new Vue({
  // ...组件选项
})

也可以传入一个可选的选项对象:

Vue.use(MyPlugin, { someOption: true })

Vue.use 会自动阻止多次注册相同插件,届时即使多次调用也只会注册一次该插件。

插件的注册原理

明白插件的使用后,我们可以看看插件是如何注册的,主要包括如何自动调用 install 方法,插件选项传递,及防止重复注册。

/**
 * Convert an Array-like object to a real Array.
 */
export function toArray (list: any, start?: number): Array<any> {
  start = start || 0
  let i = list.length - start
  const ret: Array<any> = new Array(i)
  while (i--) {
    ret[i] = list[i + start]
  }
  return ret
}
import { toArray } from '../util/index'

export function initUse (Vue: GlobalAPI) {
  Vue.use = function (plugin: Function | Object) {
    // 根据注册列表 防止重复注册
    const installedPlugins = (this._installedPlugins || (this._installedPlugins = []))
    if (installedPlugins.indexOf(plugin) > -1) {
      return this
    }

    // additional parameters 移除第一个参数(插件)
    const args = toArray(arguments, 1)

    // 根据this指向调用者 this === Vue
    args.unshift(this)

    // 兼容
    if (typeof plugin.install === 'function') {
      // 插件选项传递 此时第一个参数为Vue
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    // 缓存注册列表
    installedPlugins.push(plugin)
    return this
  }
}

use 方法的源码非常简单,从函数的注释中我们可以很清楚地解释上面的疑问。

开发插件

插件的开发其实就是利用 use 方法中传入的 Vue 函数,利用 Vue.directive Vue.component 等全局方法来实现我们的功能。

MyPlugin.install = function (Vue, options) {
  // 1. 添加全局方法或 property
  Vue.myGlobalMethod = function () {
    // 逻辑...
  }

  // 2. 添加全局资源
  Vue.directive('my-directive', {
    bind (el, binding, vnode, oldVnode) {
      // 逻辑...
    }
    ...
  })

  // 3. 注入组件选项
  Vue.mixin({
    created: function () {
      // 逻辑...
    }
    ...
  })

  // 4. 添加实例方法
  Vue.prototype.$myMethod = function (methodOptions) {
    // 逻辑...
  }
}

可能你和我会有一样的疑惑,为什么需要使用 use 调用插件来完成注册并实现功能,在 use 之外不是也可以正常调用 Vue 的静态方法来实现我们需要的功能么?答案当然可以,但我想在 Vue 中定义插件应该是为了更好的封装及解耦,将插件代码独立封装,和业务代码解耦,同时定义了插件的统一安装注册规范,标准化让调用更加简单且统一。

插件开发实例

网上有很多插件开发实例,这里借用别人的一个loading插件实现。

首先使用组件的方式来创建UI

<template>
  <div class="container" v-show="show">
    <div class="loading">
      <span></span>
      <span></span>
      <span></span>
      <span></span>
      <span></span>
    </div>
  </div>
</template>

<script>
export default {
  data () {
    return {
      show: false
    };
  }
};
</script>

<style lang="less" scoped>
.container {
  display: flex;
  justify-content: center;
  align-items: center;
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  z-index: 1000;

  span {
    display: inline-block;
    width: 8px;
    height: 80px;
    background: rgb(255, 107, 186);
    border-radius: 4px;
    animation: load 1.6s ease infinite;
  }

  @keyframes load {
    0%,
    100% {
      height: 80px;
      background: rgb(255, 107, 186);
    }
    50% {
      height: 20px;
      margin-top: 60px;
      background: rgb(245, 15, 53);
    }
  }

  span:nth-child(2) {
    animation-delay: 0.2s;
  }

  span:nth-child(3) {
    animation-delay: 0.4s;
  }

  span:nth-child(4) {
    animation-delay: 0.6s;
  }

  span:nth-child(5) {
    animation-delay: 0.8s;
  }
}
</style>

编写插件函数

import LoadingComponent from './loading.vue';

// 组件实例
let $vm;

const MyPlugin = {
  install (Vue) {
    if (!$vm) {
      // 通过extend创建子类并挂载节点
      const TemplateConstructor = Vue.extend(LoadingComponent);
      $vm = new TemplateConstructor().$mount(document.createElement('div'));
      
      document.body.appendChild($vm.$el);
    }

    $vm.show = false;
    
    // 这边其实是个闭包的运用
    const loading = {
      show () {
        $vm.show = true;
      },
      hide () {
        $vm.show = false;
      }
    };

    // 挂载在原型对象 实现vue实例this调用
    Vue.prototype.$loading = loading;
  }
};

export default MyPlugin;

注册插件

import MyPlugin from './loading'

Vue.use(MyPlugin);

在vue实例中使用

mounted() {
  this.$loading.show();
}

总结

学习Vue.use的原因是之前面试的时候遇到面试官问Vue.use的原理没有回答出来,其实如果有去了解过的都能弄明白,原理很简单。谁让自己没去了解过呢,只好哑巴吃黄连了。下次你再问我试试?

参考


欢迎到前端菜鸟群一起学习交流~516913974