Vue3封装Toast组件

5,250 阅读1分钟

前言

最近在用vue3重构老的项目,遇到了toast组件重写,顺便探索了一番,主要学习了Vant和Element-ui的ui封装方法

准备需求

  1. 希望构建一个可以api调用,并挂载到Vue实例上,可以在SFC中使用this.$toast进行调用
  2. 编写一个Toast.vue模板,以供渲染
  3. 编写一个toast.ts导出toast方法,提供给Vue进行挂载
  4. 在main.ts中引用,进行全局挂载

进一步细化

1. 模板
  • 提供一个message参数为弹窗内容
  • 需要一个淡出淡入的特效,所以需要transition和一个showToast的state
2. 导出方法
  • 根据模板生成实例
  • 将实例挂载到dom
  • 提供一个duration 并设置一个时长为duration的定时器
  • 定时器执行完毕后删除dom中的实例
  • 最终导出一个可挂载的对象
3.生成实例
  • 编写一个通用的composition api
  • 导出挂载的实例
  • 导出对应实例的卸载方法

代码

1. 模板 Toast.vue
<style lang="less">
.toast {
  position: fixed;
  left0;
  right0;
  top50%;
  transformtranslateY(-50%);
  margin: auto;
  padding0.1rem;
  max-width80%;
  border-radius0.05rem;
  color: #fff;
  backgroundrgba(0000.7);
  font-size0.28rem;
  z-index99999;
  transition: opacity 0.3s linear;
}

.toast-wrapper-enter-from,
.toast-wrapper-leave-to {
  opacity0;
}
</style>
<template>
  <transition name="toast-wrapper">
    <div class="toast" v-show="showToast">{{ message }}</div>
  </transition>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
  // 消息内容
  props: ['message'],
  data() {
    return {
      // state
      showToastfalse
    }
  }
})
</script>
2. 生成实例 useMountComponent.ts

import { Component, createApp } from 'vue'
export function useMountComponent(rootComponent: Component) {
  const app = createApp(rootComponent);
  const root = document.createElement('div');
  document.body.appendChild(root);
  return {
    instance: app.mount(root),
    unmount() {
      app.unmount();
      document.body.removeChild(root);
    },
  };
}

3. 导出方法 index.ts
import ToastConstructor from './Toast.vue'
import { nextTick, createVNode, App } from 'vue';
import { useMountComponent } from '../../lib/utils/useMountComponent'
interface ToastOption {
  message: string,
  duration: number
}

const toast = (options: ToastOption | string) => {
  const duration = typeof options !== 'string' ? options.duration : 3000
  const { instance, unmount } = useMountComponent(createVNode(
    ToastConstructor,
    {
      messagetypeof options === 'string' ? options : options.message
    }
  ));
  
  // 获取实例的代理和data 代理可以拿到dom对象 data可以设置state
  const { proxy, data } = instance.$
  const RemoveSelf = function (event: TransitionEvent) {
    unmount()
  }
  
  // 在toast实例挂载到dom上之后执行 展示与消失
  nextTick(() => {
    data.showToast = true
    proxy?.$el.removeEventListener('transitionend'RemoveSelf)
    ~duration && (setTimeout(function () {
      data.showToast = false
      proxy?.$el.addEventListener('transitionend'RemoveSelf)
    }, duration));
  });
  return instance;
}


export default {
  // 挂载对象需要提供 install方法
  install(app: App) => {
    app.config.globalProperties.$toast = toast
  }
}
4. 全局挂载 main.ts

import { createApp } from 'vue'
import Toast from './components/toast/index'

const app = createApp(App)
app.use(Toast)
5. 调用

export default defineComponent({
  name"TestToast",
  setup() { 
     const { proxy } = getCurrentInstance() as ComponentInternalInstance
     proxy?.$toast('test in setup'); 
   },
   methods:{
    TestToast() {
       this.$toast('test in method')
    }
   }
});

总结

Vue3中获取实例与修改实例的属性等操作感觉有点奇怪 并没有vue2中那么方便 这也许就是proxy带来的一点小的副作用吧