Vue插件要点分析

457 阅读3分钟

一、vue.use源码分析

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)
    args.unshift(this)
    if (typeof plugin.install === 'function') {
      plugin.install.apply(plugin, args)
    } else if (typeof plugin === 'function') {
      plugin.apply(null, args)
    }
    installedPlugins.push(plugin)
    return this
  }
}

  • 要点一:避免重复安装,首先会有检验是否已经安装过了,是的话,直接返回
  • 要点二:

如果插件有install方法,则直接调用install方法

如果插件本身是一个方法,则直接运行

将已经安装的插件做记录,避免重复安装

二、vue封装插件的常用方法

1. 添加全局方法或属性,如vue-custom-element

MyPlugin.install = function (Vue, options) { 
// 第一种方法. 添加全局方法或属性 
    Vue.myGlobalMethod = function () { 
    // 逻辑... 
    }
 }

注册方法

import registerCustomElement from './utils/registerCustomElement';
import { getProps, convertAttributeValue } from './utils/props';

function install(Vue) {
  Vue.customElement = function vueCustomElement(tag, componentDefinition, options = {}) {
    const isAsyncComponent = typeof componentDefinition === 'function';
    const optionsProps = isAsyncComponent && { props: options.props || [] };
    const props = getProps(isAsyncComponent ? optionsProps : componentDefinition);
    // register Custom Element
    const CustomElement = registerCustomElement(tag, {
      // 相关操作
    });

    return CustomElement;
  };
}

export default install;

if (typeof window !== 'undefined' && window.Vue) {
  window.Vue.use(install);
  if (install.installed) {
    install.installed = false;
  }
}

使用方法

  Vue.customElement('widget-vue', {
  props: [
    'prop1',
    'prop2',
    'prop3'
  ],
  data: {
    message: 'Hello Vue!'
  },
  template: '<p>{{ message }}, {{ prop1  }}, {{prop2}}, {{prop3}}</p>'
});
<widget-vue prop1="1" prop2="string" prop3="true"></widget-vue>

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

vueTouch.install = function (Vue) {

  Vue.directive('touch', {

    isFn: true,
    acceptStatement: true,
    priority: Vue.directive('on').priority,

    bind: function () {
      // 绑定时的操作
    },

    update: function (fn) {
      // 更新时的操作
    },

    unbind: function () {
      // 解绑时的操作
    }
  })
}


3. 通过mixin方法添加一些组件选项,如vue-router

插件内部定义:

  Vue.mixin({
    beforeCreate () {
      if (isDef(this.$options.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)
    }
  })

外部使用:

new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

所以每个组件在执行beforeCreate生命周期的时候,都会执行this._router.init(this)

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

Vue.prototype.$myMethod = function (methodOptions) 
{ // 逻辑... }

5. 全局注册组件,如elementUI

// 第六种方法,注册组件 
Vue.component(组件名, 组件)

三、开放封闭原则

对拓展开放,意味着有新的需求或变化是,可以对现有代码进行扩展,以适应新的情况

对修改封闭,意味着类一旦设计完成,就可以独立完成其工作,而不要对类进行任何修改

规则建议:

1、开放封闭原则,是最为重要的设计原则,Liskov替换原则和合成/聚合复用原则为开放封闭原则的实现提供保证。

2、可以通过Template Method模式和Strategy模式进行重构,实现对修改封闭、对扩展开放的设计思路。

3、封装变化,是实现开放封闭原则的重要手段,对于经常发生变化的状态一般将其封装为一个抽象,例如银行业务中的IBankProcess接口。

4、拒绝滥用抽象,只将经常变化的部分进行抽象,这种经验可以从设计模式的学习与应用中获得。

四、插件的开发原则

  • 插件对外暴露的参数应保持最少原则,聚焦使用者关注点
  • 插件或组件的实现应该要基于使用场景考虑
  • 开发一款组件或插件,应该保持软件工程领域的开放封闭原则
  • 一款好的插件或组件不是一蹴而就的,,往往需要后期使用过程中发现问题,加以改善
    • 组件或者插件的文档一定要完善,并不是每一个使用它的人,都关心它的内部实现,他们更关心的可能仅是如何快速上手

五、核心代码

toast插件

只可以通过函数式进行调用

import Toast from './component'
import { iconsMap, titleMap } from './config'
const ToastPlugin = {
  install (Vue, options = {}) {
    const ToastConstructor = Vue.extend(Toast)
    Vue.prototype.$toast = toast

    function buildProps (args) {
      let props = {}
      // 配置项操作
      return props
    }

    function toast () {
      if (!arguments[0]) return
      const propsData = buildProps(arguments)
      const instance = new ToastConstructor({ propsData })
      document.body.appendChild(instance.$mount().$el)
    }
  }
}

export default ToastPlugin

文字输入包括表情插件

import Emoji from './App.vue'
const components = [
  Emoji
]

const install = function (Vue, opts = {}) {
  components.map(component => {
    Vue.component(component.name, component);
  })
}

/* 支持使用标签的方式引入 */
if (typeof window !== 'undefined' && window.Vue) {
  install(window.Vue);
}
export default {
  install,
  Emoji
}

vue.use安装插件的时候,会直接执行install方法,即进行了全局注册,便可以通过标签进行调用了(elementUI)