美文网首页vue收录VueVue
开发Vue插件四种方式

开发Vue插件四种方式

作者: Evtion | 来源:发表于2019-06-11 21:31 被阅读193次

    在日常开发中,可能只需要一两个插件就可以完成对系统开发需要。如果引入整个组件库,最后打包项目体积比较大。例如element-ui中的message提示组件等。下面会在vuejs官网提供的插件建议,根据四种方法开发不同的vuejs插件

    插件

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

    1. 添加全局方法或者属性。如: vue-custom-element
    2. 添加全局资源:指令/过滤器/过渡等。如 vue-touch
    3. 通过全局混入来添加一些组件选项。如 vue-router
    4. 添加 Vue 实例方法,通过把它们添加到 Vue.prototype 上实现。
    5. 一个库(例如element-ui),提供自己的 API,同时提供上面提到的一个或多个功能。如vue-router

    initUse安装插件函数

    vuejs源码src/core/global-api/use.js中可以阅读到vuejs插件需要export一个install函数。vue使用indexOf检测插件是否已注册,防止插件的重复注册。

    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
      }
    }
    

    指令directive方式开发插件

    一个指令定义对象可以提供如下几种钩子函数(都为可选):

    指令钩子函数

    • bind:只调用一次,指令第一次绑定到元素时调用。在这里可以进行一次性初始化设置
    • inserted:被绑定元素插入父节点时调用(仅保证父节点存在,但不一定已被插入文档中)。
    • update:所在组件的VNode更新时调用,但是可能发生在其子VNode更新之前。指令值可能发生了变化,也可能没有发生变华。
    • componentUpdated:指令所在组件的VNode及子VNode全部更新后调用
    • unbind:只调用一次,指令与元素解绑时调用

    钩子函数参数

    指令钩子函数会被传入以下参数:

    • el:指令所绑定的元素,可以用来直接操作DOM
    • binding:一个对象,包含以下属性
      • name:指令名,不包含v-前缀
      • value:指令的绑定值,例如:v-my-direactive= "1+1"中表达式值为2
      • oldValue:指令绑定的前一个值,仅在 updatecomponentUpdated 钩子中可用。无论值是否改变都可用。
      • expression:字符串形式的指令表达式。例如 v-my-directive="1 + 1"中,表达式为 "1 + 1"
    • arg:传给指令的参数,可选。例如 v-my-directive:foo中,参数为 foo
    • modifiers:一个包含修饰符的对象。例如:v-my-directive.foo.bar 中,修饰符对象为 { foo: true, bar: true }

    上面说明文档是在vuejs官方文档教程中摘抄的,可以通过这里阅读而更多

    v-time插件

    vill-directive插件地址

    vuejs插件需要提供一个install函数向外暴露。在src/directives/time下的index.js中一共开发了三个不同关于time的指令v-timev-clockv-down

    import Time from "./time.js";
    export default {
      install(Vue, options = {}) {}
    };
    
    • v-time显示当前时间指令

    v-time获取一个传入的时间戳值binding.value,然后返回一个符合格式的time

    import Time from "./time.js";
    export default {
      install(Vue, options = {}) {
         Vue.directive("time", {
            bind(el, binding) {
              el.innerHTML = el.innerHTML ? el.innerHTML : el.textContent;
              el.innerHTML = Time.getFormatTime(binding.value);
            }
         });
      }
    };
    
    • v-clock时钟指令
      v-clock每隔一秒获取一次当前时间实现时钟的效果。
    import Time from "./time.js";
    export default {
      install(Vue, options = {}) {
        Vue.directive("clock", {
          bind(el, binding) {
            el.timeout = setInterval(function() {
              const value = Date.now();
              el.innerText = Time.getFormatTime(value);
            }, 1000);
          },
          unbind() {
            clearInterval(el.timeout);
            delete el.timeout;
          }
        });
      }
    };
    
    • v-down给定时间倒计时指令

    v-down传入未来的一个时间戳,计算倒计时间。

    import Time from "./time.js";
    export default {
      install(Vue, options = {}) {
        Vue.directive("down", {
          bind(el, binding) {
            const value = binding.value;
            el.__handle__ = setInterval(() => {
              if (Time.getDownTime(value).clear) {
                clearInterval(el.__handle__);
                el.innerText = Time.getDownTime(value).time;
                return;
              }
              el.innerText = Time.getDownTime(value);
            }, 1000);
          },
          unbind() {
            clearInterval(el.__timeout__);
            delete el.__timeout__;
          }
        });
      }
    };
    
    • 格式化时间函数

    getFormatTimeYYYY-MM-DD hh:mm:ss时间格式的函数,computeTime是计算传入时间与当前时间差值得格式时间。

    export default {
      getFormatTime(value) {
        if (isNaN(value)) {
          console.error("the value is not a number");
          return;
        }
        const date = new Date(value);
        const year = date.getFullYear();
        const month = this.format(date.getMonth() + 1);
        const day = this.format(date.getDay());
        const hours = this.format(date.getHours());
        const minutes = this.format(date.getMinutes());
        const seconds = this.format(date.getSeconds());
        return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
      },
    
      format(value) {
        return value > 9 ? value : "0" + value;
      },
      getDownTime(value) {
        const date = new Date(value);
        const now = Date.now();
        const count = (date - now) / 1000;
        if (count <= 0) {
          return {
            clear: true,
            time: "00天00时00分00秒"
          };
        }
        return this.computeTime(count);
      },
      computeTime(value) {
        const day = this.format(Math.floor(value / 86400));
        const hours = this.format(Math.floor((value % 86400) / 3600));
        const minutes = this.format(Math.floor(((value % 86400) % 3600) / 60));
        const seconds = this.format(Math.floor(((value % 86400) % 3600) % 60));
        return `${day}天${hours}时${minutes}分${seconds}秒`;
      }
    };
    

    使用和运行效果

    • 使用方法
    //在入口文件main.js
    import time from 'vill-directive'
    Vue.use(time);
    //其他组件
    <template>
      <div class="demo">
        <h4>v-time 指令</h4>
        <span v-time="now"></span>
        <h4>v-clock 指令</h4>
        <span v-clock></span>
        <h4>v-down 指令</h4>
        <span v-down="time"></span>
      </div>
    </template>
    <script>
    export default {
      name: "Demo",
      data() {
        return {
          time: "2019-03-20 13:16:00"
        };
      },
      computed: {
        now() {
          return Date.now();
        }
      }
    };
    </script>
    <style scoped></style>
    
    • 运行效果
    demo

    prototype方式开发插件

    通过prototype方式开发插件,是在vue的原型上添加属性或者方法。例如:

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

    message提示插件

    message插件地址

    • 挂载方法或属性到prototype

    src/lib/vill-message中的index.js中,定义了vuejsinstall函数,主要是把方法和属性添加到vue的原型上。

    import Message from './main.js';
    export default {
        install(Vue,options={}){
            Vue.prototype.$message = Message;
        }
    };
    
    • 引入DOM元素并进行挂载

    Vue.extend使用基础 Vue 构造器,创建一个“子类”。options是可以传入的参数配置。然后对数据进行手动挂载$mount,最后加入document文档中。

    import Vue from "vue";
    import MessageTpl from "./message.vue";
    const NoticeConstructor = Vue.extend(MessageTpl);
    let nId = 1;
    const Message = options => {
      if(JSON.stringify(options) == undefined)
        return false
      let id = "notice-" + nId++;
      options = options || {};
      if (typeof options === "string") {
        options = {
          message: options
        };
      }
    
      const NoticeInstance = new NoticeConstructor({
        data: options
      });
      NoticeInstance.id = id;
      NoticeInstance.vm = NoticeInstance.$mount();
      NoticeInstance.vm.visible = true;
      NoticeInstance.dom = NoticeInstance.vm.$el;
      document.body.appendChild(NoticeInstance.dom);
      return NoticeInstance.vm;
    };
    ["success", "warning", "info", "error"].forEach(type => {
      Message[type] = options => {
        if (typeof options === "string") {
          options = {
            message: options
          };
        }
        options.type = type;
        return Message(options);
      };
    });
    export default Message;
    
    • message.vue模板

    message是传入的参数值,是提示的内容值;Icon是一个图标组件。

     <template>
      <transition name="vill-message-fade">
        <div v-if="visible" :class="wrapClasses">
            <Icon :iconType="type"></Icon>
            <span :class="[prefixCls+'-content']">
              {{message}}
            </span>
        </div>        
      </transition>
    </template>
    
    <script>
    const prefixCls = "vill-message";
    import Icon from "../vill_icon/index.js";
    export default {
      name: "vill-message",
      data() {
        return {
          visible: false,
          type: "info",
          message: "",
          duration: 1500,
          prefixCls: prefixCls
        };
      },
      components: {
        Icon: Icon
      },
      computed: {
        wrapClasses() {
          return [`${prefixCls}`, `${prefixCls}-${this.type}`];
        }
      },
      methods: {
        setTimer() {
          setTimeout(() => {
            this.close(); // 3000ms之后调用关闭方法
          }, this.duration);
        },
        close() {
          this.visible = false;
          setTimeout(() => {
            this.$destroy(true);
            this.$el.parentNode.removeChild(this.$el); // 从DOM里将这个组件移除
          }, 500);
        }
      },
      mounted() {
        this.setTimer(); // 挂载的时候就开始计时,3000ms后消失
      }
    };
    </script>
    

    Icon图标组件采取的render函数进行渲染,根据传入的参数successerrorwarninginfo,直接渲染同名SVG图标(有点取巧),这样避免v-ifv-else的多个条件判断的做法。

    <script>
    const prefixCls = "vill-icon";
    export default {
      name: "vill-icon",
      data() {
        return {
          prefixCls: prefixCls
        };
      },
      props: {
        iconType: {
          type: String,
          default: "info"
        }
      },
      render: function(h) {
        return h(
          "i",
          {
            attrs: {
              class: `${this.prefixCls}`
            }
          },
          [
            h("img", {
              attrs: {
                src: require(`./assets/${this.iconType}.svg`),
                class: "icon"
              }
            })
          ]
        );
      }
    };
    </script>
    <style scoped lang="scss">
    .vill-icon {
      display: inline-block;
      width: 20px;
      height: 20px;
      color: #fff;
      overflow: hidden;
      border-radius: 50%;
      margin: 0 15px;
      & .icon {
        display: inline-block;
        width: 100%;
        height: 100%;
      }
    }
    </style>
    
    • 使用方式
    //在main入口
    import message from 'vill-message'
    Vue.use(message);
    //在其他vue文件
    this.$message({
        duration:2000,
        type:'success',
        message:'hello vill-message'
    });
    this.$message({
        duration:2000,
        type:'error',
        message:'hello vill-message'
    });
    this.$message.success("hello vill-message");
    this.$message.error("hello vill-message");
    this.$message.warning("hello vill-message");
    this.$message.info("hello vill-message");
    
    • 运行效果
    demo1

    Mixin开发插件

    "混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。"

    mixin使用比较简单,可以定义常用method或者生命周期函数在Minxin中,然后混入各组件之中。

    // 定义一个混入对象
    var myMixin = {
      created: function () {
        this.hello()
      },
      methods: {
        hello: function () {
          console.log('hello from mixin!')
        }
      }
    }
    
    // 定义一个使用混入对象的组件
    var Component = Vue.extend({
      mixins: [myMixin]
    })
    
    var component = new Component() // => "hello from mixin!"
    

    添加Vue全局方法或者属性方式

    添加vue全局方法和属性开发vue插件跟prototype比较类似,差别只是在把属性或者方法绑定在prototype改成直接绑定在vue实例上。如下所示:

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

    其他message.vue组件模板完全和prototype原型上一样。

    如果觉得喜欢可以给个赞~

    vill-directive地址:https://github.com/Harhao/vill-directive

    vill-message地址:https://github.com/Harhao/vill-messgae

    相关文章

      网友评论

        本文标题:开发Vue插件四种方式

        本文链接:https://www.haomeiwen.com/subject/eigcfctx.html