写一个 Vue 的插件 toast

2022-08-15 08:28:24 浏览数 (2)

本人在造轮子过程中遇到写 toast 组件时为考虑方便用户调用,因此采用插件方式写 toast,

最终用户调用代码为

代码语言:javascript复制
<template>
   <button @click="showToast"></button>
</template>

<script>
  export default {
    methods: {
     showToast() {
      this.$toast(`您的余额为 ${parseInt(Math.random() * 100)}. 需要充值`, {
        enableHtml: false, // 是否开启内嵌html元素
        autoClose: false, // 是否自动关闭
        position: 'top', // 展示位置
        closeButton: {  // 关闭按钮配置
          text: '充值',
          callback: (toast) => {
            toast.log() // 关闭后触发 toast 中的方法
          }
        }
      })
    }
  }
</script>

展示效果为

插件代码

代码语言:javascript复制
import Toast from './toast.vue'

let currentToast
export default {
  install(Vue, options) {
    Vue.prototype.$toast = (message, toastOptions) => {
      // 避免出现多个 toast 重叠
      if(currentToast) {
        currentToast.close()
      }

      currentToast = createToast({
        Vue,
        message,
        propsData: toastOptions,
        onClose: () => {
          currentToast = null
        }
      })
    }
  }
}

function createToast({ Vue, message, propsData, onClose }) {
  const Constructor = Vue.extend(Toast)
  let toast = new Constructor({
    propsData
  })
  toast.$slots.default = [message]
  toast.$mount()
  toast.$on('close', onClose)
  document.body.appendChild(toast.$el)
  return toast
}

toast 组件代码

代码语言:javascript复制
<template>
  <div class="wrapper" :class="toastClasses">
    <div class="toast" ref="toast">
      <div class="message">
        <slot v-if="!enableHtml"></slot>
        <div v-else v-html="$slots.default[0]"></div>
      </div>
      <div class="line" ref="line"></div>
      <span class="close" v-if="closeButton" @click="onClickClose">{{closeButton.text}}</span>
    </div>
  </div>
</template>
<script>
export default {
  name: "WheelToast",
  props: {
    autoClose: {
      type: [Boolean, Number],
      default: 5,
      validator(value) {
        return typeof value === 'boolean' || typeof value === 'number'
      }
    },
    closeButton: {
      type: Object,
      // 传 object 时得用函数返回,不然属性会被覆盖,跟 data 一个道理
      default() {
        return {
          text: "关闭",
          // 点击关闭按钮的回调函数
          callback: undefined
        }
      }
    },
    enableHtml: {
      type: Boolean,
      default: false
    },
    position: {
      type: String,
      default: "middle",
      validator(value) {
        return ["top", "middle", "bottom"].indexOf(value) >= 0;
      }
    }
  },
  computed: {
    toastClasses() {
      return [`position-${this.position}`];
    }
  },
  mounted() {
    this.updateStyle();
    this.execAutoClose();
  },
  methods: {
    updateStyle() {
      // 实现多行文字父元素需要用到 min-height,此时子元素 height 为 100% 获取不到父元素 height,因此异步获取父元素高
      this.$nextTick(() => {
        this.$refs.line.style.height = `${this.$refs.toast.getBoundingClientRect().height}px`;
      });
    },
    execAutoClose() {
      if (this.autoClose) {
        setTimeout(() => {
          this.close();
        }, this.autoClose * 1000);
      }
    },
    close() {
      this.$el.remove();
      this.$emit("close");
      this.$destroy();
    },
    log() {
      console.log("调用了 toast 的 log 方法");
    },
    onClickClose() {
      this.close();
      if (this.closeButton && typeof this.closeButton.callback === "function") {
        // 调用 callback 传入 this 可让调用回调时拿到 toast 实例,从而调用实例里的方法
        this.closeButton.callback(this);
      }
    }
  }
};
</script>

备注:为了简洁去掉了 css 代码

使用插件

代码语言:javascript复制
import Vue from 'vue'
import plugin from './plugin'

Vue.use(plugin)

之后便可以在组件中用this.$toast(MESSAGE, OPTIONS)的方式调用 toast插件啦

总结

制作toast插件的详细过程可以查看我的造轮子项目https://github.com/zyqq/wheel/tree/toast toast分支下的commit记录

0 人点赞