開發Vue外掛四種方式

Harhao發表於2019-06-11

在日常開發中,可能只需要一兩個外掛就可以完成對系統開發需要。如果引入整個元件庫,最後打包專案體積比較大。例如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.getDate());
    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地址:github.com/Harhao/vill…

vill-message地址:github.com/Harhao/vill…

相關文章