开发Vue插件四种方式

在日常开发中,可能只需要一两个插件就可以完成对系统开发需要。如果引入整个组件库,最后打包项目体积比较大。例如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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 207,113评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,644评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,340评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,449评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,445评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,166评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,442评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,105评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,601评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,066评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,161评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,792评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,351评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,352评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,584评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,618评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,916评论 2 344

推荐阅读更多精彩内容

  • Vue 实例 属性和方法 每个 Vue 实例都会代理其 data 对象里所有的属性:var data = { a:...
    云之外阅读 2,202评论 0 6
  • 基于Vue的一些资料 内容 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 element★...
    尝了又尝阅读 1,140评论 0 1
  • 这篇笔记主要包含 Vue 2 不同于 Vue 1 或者特有的内容,还有我对于 Vue 1.0 印象不深的内容。关于...
    云之外阅读 5,045评论 0 29
  • UI组件 element- 饿了么出品的Vue2的web UI工具套件 Vux- 基于Vue和WeUI的组件库 m...
    小姜先森o0O阅读 9,397评论 0 72
  • UI组件 element- 饿了么出品的Vue2的web UI工具套件 Vux- 基于Vue和WeUI的组件库 m...
    柴东啊阅读 15,848评论 2 140