vuejs怎样封装一个插件(以封装vue-toast为例扩展)

  • A+
所属分类:Web前端
摘要

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


插件介绍

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

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

Vue.js 的插件应当有一个公开方法 install 。这个方法的第一个参数是 Vue 构造器,第二个参数是一个可选的选项对象:

MyPlugin.install = function (Vue, options) {   // 1. 添加全局方法或属性   Vue.myGlobalMethod = function () {     // 逻辑...   }    // 2. 添加全局资源   Vue.directive('my-directive', {     bind (el, binding, vnode, oldVnode) {       // 逻辑...     }     ...   })    // 3. 注入组件   Vue.mixin({     created: function () {       // 逻辑...     }     ...   })    // 4. 添加实例方法   Vue.prototype.$myMethod = function (methodOptions) {     // 逻辑...   } }

使用方式很简单,通过全局方法Vue.use()来使用插件。

下面开发一个简单的在控制台打印消息插件,新建一个toast.js:

var Toast = {}; Toast.install = function (Vue, options) {     Vue.prototype.$msg = 'Hello World'; } export default Toast;

在main.js中引入该toast.js并使用vue.use()安装该toast插件:

import Vue from 'vue'; import Toast from './toast.js'; Vue.use(Toast);

然后我们在任意vue页面就可以调用:

export default {     mounted(){         console.log(this.$msg);         // Hello World     } }

 开发vue-toast插件

该插件实现的效果:

  • 1.提示内容可以显示在不同的位置(顶部、底部、中间),调用方式this.$toast.top('填写的提示内容')this.$toast.center('填写的提示内容')this.$toast.bottom('填写的提示内容')
  • 2.通过this.$toast('填写的提示内容')调用该插件,默认展示在底部;
  • 3.toast有默认的展示持续时间,也可配置toast展示的持续时间。

toast.js实现代码如下:

let Toast = {}; Toast.install = function(Vue,options) {   let opts = {     type: 'bottom', // 显示位置     duration: 2500  // 持续时间   }   for(let prop in options) {     if(options.hasOwnProperty(prop)) {       opts[prop] = options[prop];     }   }      Vue.prototype.$toast = (tips,type) => {     if(type) {       opts.type = type;     }     if(document.getElementsByClassName('vue-toast').length) {       return false;     }     let ToastEl = Vue.extend({       template: `<div class="vue-toast vue-toast__${opts.type}">${tips}</div>`     })     let toastElement = new ToastEl().$mount().$el;     document.body.append(toastElement);     setTimeout(() => {       document.body.removeChild(toastElement);     },opts.duration);   }      ['top','center','bottom'].forEach((type) => {     Vue.prototype.$toast[type] = (tips) => {      return Vue.prototype.$toast(tips,type)     }   }) }  export default Toast;

 优化vue-toast

调用方式更通用,如下所示:

this.toast = this.$toast({         message: '这是一段信息',         position: 'top',         duration: 0       });

可以手动关闭弹框

this.toast.close();

实现方式就是封装成一个构造函数,整体代码如下:

function Toast() {   this.toastTimer = false; // toastTimer:toast定时器   this.toastVM = null; // toastVM:存储toast VM   this.install = function(Vue,options) {     let opts = {       message: '', // 文本内容       position: 'bottom', // 显示位置       duration: 2500,  // 展示时长(ms),值为 0 时,toast 不会消失       className: ''  // 自定义类名     }      /** toast提示方法      * @params {Object|String} config 配置,如果为字符串=message      * */     Vue.prototype.$toast = (config) => {       let option = {};       let self = this;       Object.assign(option,opts,options);        if(typeof config === 'object') {         Object.assign(option,config)       } else { // 如果是字符串,传递的是message文本内容         option.message = config;       }        if(this.toastTimer) { // 如果toast还在,则取消上次消失时间         clearTimeout(this.toastTimer);         this.toastVM.show = false;       }        if(!this.toastVM) {         let ToastEl = Vue.extend({           data() {             return {               show: false,               message: option.message,               className: option.className             }           },           methods: {             // 关闭toast             close() {               self.toastVM.show = false;             }           },           render(createElement) {             if(!this.show) {               return false;             }             return createElement(             'div',             {               class: ['lx-toast',`lx-toast-${option.position}`,this.className],               show: this.show,               domProps: {                 innerHTML: this.message               }             },             )           }         })         this.toastVM = new ToastEl();         document.body.append(this.toastVM.$mount().$el);       }       this.toastVM.show = true;       this.toastVM.message = option.message;       this.toastVM.className = option.className;       this.toastVM.position = option.position;        if(option.duration != 0) {// 为0的时候一直显示         this.toastTimer = setTimeout(() => {             this.toastVM.show = this.toastTimer = false;         },option.duration)       }       return this.toastVM;     }   } }  export default new Toast();

使用方式:

<template>   <div class="test">     <button @click="open">开启弹框</button>     <button @click="close">关闭弹框</button>   </div> </template>  <script> export default {   data () {     return {       toast: null     }   },    methods: {     open() {       this.toast = this.$toast({         message: '这是一段信息',         position: 'top',         duration: 0       });     },     close() {       this.toast.close();     }   } } </script>

添加loading方法

完整代码:

function Toast() {   this.toastTimer = false; // toastTimer:toast定时器   this.toastVM = null; // toastVM:存储toast VM   this.showLoad = false;  // 存储loading显示状态   this.loadNode = null; // 存储loading节点元素   this.install = function(Vue,options) {     let opts = {       message: '', // 文本内容       position: 'bottom', // 显示位置       duration: 2500,  // 展示时长(ms),值为 0 时,toast 不会消失       className: ''  // 自定义类名     }      /** toast提示方法      * @params {Object|String} config 配置,如果为字符串=message      * */     Vue.prototype.$toast = (config) => {       let option = {};       let self = this;       Object.assign(option,opts,options);        if(typeof config === 'object') {         Object.assign(option,config)       } else { // 如果是字符串,传递的是message文本内容         option.message = config;       }        if(this.toastTimer) { // 如果toast还在,则取消上次消失时间         clearTimeout(this.toastTimer);         this.toastVM.show = false;       }        if(!this.toastVM) {         let ToastEl = Vue.extend({           data() {             return {               show: false,               message: option.message,               className: option.className             }           },           methods: {             // 关闭toast             close() {               self.toastVM.show = false;             }           },           render(createElement) {             if(!this.show) {               return false;             }             return createElement(             'div',             {               class: ['lx-toast',`lx-toast-${option.position}`,this.className],               show: this.show,               domProps: {                 innerHTML: this.message               }             },             )           }         })         this.toastVM = new ToastEl();         document.body.append(this.toastVM.$mount().$el);       }       this.toastVM.show = true;       this.toastVM.message = option.message;       this.toastVM.className = option.className;       this.toastVM.position = option.position;        if(option.duration != 0) {// 为0的时候一直显示         this.toastTimer = setTimeout(() => {             this.toastVM.show = this.toastTimer = false;         },option.duration)       }       return this.toastVM;     }      /** $loading加载      * @params {Object|String} config 配置,如果为字符串=message      * */     Vue.prototype.$loading = (config) => {       let option = {};       let self = this;       if(typeof config === 'object') {         Object.assign(option,config)       } else { // 传递的字符串         option.message = config;       }        if(option.type == 'close') {         if(this.loadNode) {            this.loadNode.show = this.showLoad = false;         }       } else {         if(this.showLoad && this.loadNode) {           this.loadNode.message = option.message;           return false;         }         const loadEl = Vue.extend({           data() {             return {               show: false,               message: option.message             }           },           methods: {             close() {               self.loadNode.show = self.showLoad = false;             }           },           render(h) {             if(!this.show) {               return false;             }             return h('div',{               class: 'lx-load-mark',               show: this.show             },[               h('div',{                 class: 'lx-load-box'               },[                 h('div',{                   class: this.message ? 'lx-loading':'lx-loading-nocontent'                 },Array.apply(null,{length: 12}).map((value,index) => {                   return h('div',{                     class: ['loading_leaf',`loading_leaf_${index}`]                   })                 })),                 h('div',{                   class: 'lx-load-content',                   domProps: {                     innerHTML: this.message                   }                 })               ])             ])           }         })          this.loadNode = new loadEl();         document.body.appendChild(this.loadNode.$mount().$el);         this.loadNode.show = this.showLoad = true;       }       return this.loadNode;     }      ['open','close'].forEach(type => {       Vue.prototype.$loading[type] = (message) => {         return Vue.prototype.$loading({type,message})       }     })   } }  export default new Toast();

完整的调用方式:

<template>   <div class="test">     <button @click="open">开启弹框</button>     <button @click="close">关闭弹框</button>   </div> </template>  <script> export default {   data () {     return {       toast: null     }   },    mounted() {     let loading = this.$loading({       message: '这是loading文字'     })      setTimeout(() => { // 5s 后关闭弹框       loading.close();     },5000);   },    methods: {     open() {       this.toast = this.$toast({         message: '这是一段信息',         position: 'top',         duration: 0       });     },     close() {       this.toast.close();     }   } } </script>

 toast.css代码:

.lx-toast {     position: fixed;     bottom: 100px;     left: 50%;     -webkit-box-sizing: border-box;             box-sizing: border-box;     max-width: 80%;     height: 40px;     line-height: 20px;     padding: 10px 20px;     transform: translateX(-50%);     -webkit-transform: translateX(-50%);     text-align: center;     z-index: 9999;     font-size: 14px;     color: #fff;     border-radius: 5px;     background: rgba(0, 0, 0, 0.7);     animation: show-toast .5s;     -webkit-animation: show-toast .5s;     overflow: hidden;     text-overflow: ellipsis;     white-space: nowrap; }  .lx-toast.lx-word-wrap {     width: 80%;     white-space: inherit;     height: auto; }  .lx-toast.lx-toast-top {     top: 50px;     bottom: inherit; }  .lx-toast.lx-toast-center {     top: 50%;     margin-top: -20px;     bottom: inherit; }  @-webkit-keyframes show-toast {     from {         opacity: 0;     }     to {         opacity: 1;     } }  @keyframes show-toast {     from {         opacity: 0;     }     to {         opacity: 1;     } }  .lx-load-mark {     position: fixed;     left: 0;     top: 0;     width: 100%;     height: 100%;     z-index: 9999; }  .lx-load-box {     position: fixed;     z-index: 3;     width: 7.6em;     min-height: 7.6em;     top: 180px;     left: 50%;     margin-left: -3.8em;     background: rgba(0, 0, 0, 0.7);     text-align: center;     border-radius: 5px;     color: #FFFFFF; }  .lx-load-content {     margin-top: 64%;     font-size: 14px; }  .lx-loading, .lx-loading-nocontent {     position: absolute;     width: 0px;     left: 50%;     top: 38%; }  .lx-loading-nocontent {     top: 50%; }  .loading_leaf {     position: absolute;     top: -1px;     opacity: 0.25; }  .loading_leaf:before {     content: " ";     position: absolute;     width: 9.14px;     height: 3.08px;     background: #d1d1d5;     -webkit-box-shadow: rgba(0, 0, 0, 0.0980392) 0px 0px 1px;             box-shadow: rgba(0, 0, 0, 0.0980392) 0px 0px 1px;     border-radius: 1px;     -webkit-transform-origin: left 50% 0px;     transform-origin: left 50% 0px; }  .loading_leaf_0 {     -webkit-animation: opacity-0 1.25s linear infinite;     animation: opacity-0 1.25s linear infinite; }  .loading_leaf_0:before {     -webkit-transform: rotate(0deg) translate(7.92px, 0px);     transform: rotate(0deg) translate(7.92px, 0px); }  .loading_leaf_1 {     -webkit-animation: opacity-1 1.25s linear infinite;     animation: opacity-1 1.25s linear infinite; }  .loading_leaf_1:before {     -webkit-transform: rotate(30deg) translate(7.92px, 0px);     transform: rotate(30deg) translate(7.92px, 0px); }  .loading_leaf_2 {     -webkit-animation: opacity-2 1.25s linear infinite;     animation: opacity-2 1.25s linear infinite; }  .loading_leaf_2:before {     -webkit-transform: rotate(60deg) translate(7.92px, 0px);     transform: rotate(60deg) translate(7.92px, 0px); }  .loading_leaf_3 {     -webkit-animation: opacity-3 1.25s linear infinite;     animation: opacity-3 1.25s linear infinite; }  .loading_leaf_3:before {     -webkit-transform: rotate(90deg) translate(7.92px, 0px);     transform: rotate(90deg) translate(7.92px, 0px); }  .loading_leaf_4 {     -webkit-animation: opacity-4 1.25s linear infinite;     animation: opacity-4 1.25s linear infinite; }  .loading_leaf_4:before {     -webkit-transform: rotate(120deg) translate(7.92px, 0px);     transform: rotate(120deg) translate(7.92px, 0px); }  .loading_leaf_5 {     -webkit-animation: opacity-5 1.25s linear infinite;     animation: opacity-5 1.25s linear infinite; }  .loading_leaf_5:before {     -webkit-transform: rotate(150deg) translate(7.92px, 0px);     transform: rotate(150deg) translate(7.92px, 0px); }  .loading_leaf_6 {     -webkit-animation: opacity-6 1.25s linear infinite;     animation: opacity-6 1.25s linear infinite; }  .loading_leaf_6:before {     -webkit-transform: rotate(180deg) translate(7.92px, 0px);     transform: rotate(180deg) translate(7.92px, 0px); }  .loading_leaf_7 {     -webkit-animation: opacity-7 1.25s linear infinite;     animation: opacity-7 1.25s linear infinite; }  .loading_leaf_7:before {     -webkit-transform: rotate(210deg) translate(7.92px, 0px);     transform: rotate(210deg) translate(7.92px, 0px); }  .loading_leaf_8 {     -webkit-animation: opacity-8 1.25s linear infinite;     animation: opacity-8 1.25s linear infinite; }  .loading_leaf_8:before {     -webkit-transform: rotate(240deg) translate(7.92px, 0px);     transform: rotate(240deg) translate(7.92px, 0px); }  .loading_leaf_9 {     -webkit-animation: opacity-9 1.25s linear infinite;     animation: opacity-9 1.25s linear infinite; }  .loading_leaf_9:before {     -webkit-transform: rotate(270deg) translate(7.92px, 0px);     transform: rotate(270deg) translate(7.92px, 0px); }  .loading_leaf_10 {     -webkit-animation: opacity-10 1.25s linear infinite;     animation: opacity-10 1.25s linear infinite; }  .loading_leaf_10:before {     -webkit-transform: rotate(300deg) translate(7.92px, 0px);     transform: rotate(300deg) translate(7.92px, 0px); }  .loading_leaf_11 {     -webkit-animation: opacity-11 1.25s linear infinite;     animation: opacity-11 1.25s linear infinite; }  .loading_leaf_11:before {     -webkit-transform: rotate(330deg) translate(7.92px, 0px);     transform: rotate(330deg) translate(7.92px, 0px); }  @-webkit-keyframes opacity-0 {     0% {         opacity: 0.25;     }     0.01% {         opacity: 0.25;     }     0.02% {         opacity: 1;     }     60.01% {         opacity: 0.25;     }     100% {         opacity: 0.25;     } }  @keyframes opacity-0 {     0% {         opacity: 0.25;     }     0.01% {         opacity: 0.25;     }     0.02% {         opacity: 1;     }     60.01% {         opacity: 0.25;     }     100% {         opacity: 0.25;     } }  @-webkit-keyframes opacity-1 {     0% {         opacity: 0.25;     }     8.34333% {         opacity: 0.25;     }     8.35333% {         opacity: 1;     }     68.3433% {         opacity: 0.25;     }     100% {         opacity: 0.25;     } }  @keyframes opacity-1 {     0% {         opacity: 0.25;     }     8.34333% {         opacity: 0.25;     }     8.35333% {         opacity: 1;     }     68.3433% {         opacity: 0.25;     }     100% {         opacity: 0.25;     } }  @-webkit-keyframes opacity-2 {     0% {         opacity: 0.25;     }     16.6767% {         opacity: 0.25;     }     16.6867% {         opacity: 1;     }     76.6767% {         opacity: 0.25;     }     100% {         opacity: 0.25;     } }  @keyframes opacity-2 {     0% {         opacity: 0.25;     }     16.6767% {         opacity: 0.25;     }     16.6867% {         opacity: 1;     }     76.6767% {         opacity: 0.25;     }     100% {         opacity: 0.25;     } }  @-webkit-keyframes opacity-3 {     0% {         opacity: 0.25;     }     25.01% {         opacity: 0.25;     }     25.02% {         opacity: 1;     }     85.01% {         opacity: 0.25;     }     100% {         opacity: 0.25;     } }  @keyframes opacity-3 {     0% {         opacity: 0.25;     }     25.01% {         opacity: 0.25;     }     25.02% {         opacity: 1;     }     85.01% {         opacity: 0.25;     }     100% {         opacity: 0.25;     } }  @-webkit-keyframes opacity-4 {     0% {         opacity: 0.25;     }     33.3433% {         opacity: 0.25;     }     33.3533% {         opacity: 1;     }     93.3433% {         opacity: 0.25;     }     100% {         opacity: 0.25;     } }  @keyframes opacity-4 {     0% {         opacity: 0.25;     }     33.3433% {         opacity: 0.25;     }     33.3533% {         opacity: 1;     }     93.3433% {         opacity: 0.25;     }     100% {         opacity: 0.25;     } }  @-webkit-keyframes opacity-5 {     0% {         opacity: 0.270958333333333;     }     41.6767% {         opacity: 0.25;     }     41.6867% {         opacity: 1;     }     1.67667% {         opacity: 0.25;     }     100% {         opacity: 0.270958333333333;     } }  @keyframes opacity-5 {     0% {         opacity: 0.270958333333333;     }     41.6767% {         opacity: 0.25;     }     41.6867% {         opacity: 1;     }     1.67667% {         opacity: 0.25;     }     100% {         opacity: 0.270958333333333;     } }  @-webkit-keyframes opacity-6 {     0% {         opacity: 0.375125;     }     50.01% {         opacity: 0.25;     }     50.02% {         opacity: 1;     }     10.01% {         opacity: 0.25;     }     100% {         opacity: 0.375125;     } }  @keyframes opacity-6 {     0% {         opacity: 0.375125;     }     50.01% {         opacity: 0.25;     }     50.02% {         opacity: 1;     }     10.01% {         opacity: 0.25;     }     100% {         opacity: 0.375125;     } }  @-webkit-keyframes opacity-7 {     0% {         opacity: 0.479291666666667;     }     58.3433% {         opacity: 0.25;     }     58.3533% {         opacity: 1;     }     18.3433% {         opacity: 0.25;     }     100% {         opacity: 0.479291666666667;     } }  @keyframes opacity-7 {     0% {         opacity: 0.479291666666667;     }     58.3433% {         opacity: 0.25;     }     58.3533% {         opacity: 1;     }     18.3433% {         opacity: 0.25;     }     100% {         opacity: 0.479291666666667;     } }  @-webkit-keyframes opacity-8 {     0% {         opacity: 0.583458333333333;     }     66.6767% {         opacity: 0.25;     }     66.6867% {         opacity: 1;     }     26.6767% {         opacity: 0.25;     }     100% {         opacity: 0.583458333333333;     } }  @keyframes opacity-8 {     0% {         opacity: 0.583458333333333;     }     66.6767% {         opacity: 0.25;     }     66.6867% {         opacity: 1;     }     26.6767% {         opacity: 0.25;     }     100% {         opacity: 0.583458333333333;     } }  @-webkit-keyframes opacity-9 {     0% {         opacity: 0.687625;     }     75.01% {         opacity: 0.25;     }     75.02% {         opacity: 1;     }     35.01% {         opacity: 0.25;     }     100% {         opacity: 0.687625;     } }  @keyframes opacity-9 {     0% {         opacity: 0.687625;     }     75.01% {         opacity: 0.25;     }     75.02% {         opacity: 1;     }     35.01% {         opacity: 0.25;     }     100% {         opacity: 0.687625;     } }  @-webkit-keyframes opacity-10 {     0% {         opacity: 0.791791666666667;     }     83.3433% {         opacity: 0.25;     }     83.3533% {         opacity: 1;     }     43.3433% {         opacity: 0.25;     }     100% {         opacity: 0.791791666666667;     } }  @keyframes opacity-10 {     0% {         opacity: 0.791791666666667;     }     83.3433% {         opacity: 0.25;     }     83.3533% {         opacity: 1;     }     43.3433% {         opacity: 0.25;     }     100% {         opacity: 0.791791666666667;     } }  @-webkit-keyframes opacity-11 {     0% {         opacity: 0.895958333333333;     }     91.6767% {         opacity: 0.25;     }     91.6867% {         opacity: 1;     }     51.6767% {         opacity: 0.25;     }     100% {         opacity: 0.895958333333333;     } }  @keyframes opacity-11 {     0% {         opacity: 0.895958333333333;     }     91.6767% {         opacity: 0.25;     }     91.6867% {         opacity: 1;     }     51.6767% {         opacity: 0.25;     }     100% {         opacity: 0.895958333333333;     } }

参考