TS方式构建Vue3 toast组件(一)

26 阅读2分钟

组件文件夹:Toast,Toast内部文件:Toast.vue,index.ts

一、Toast.vue

<template>
  <!-- 绑定after‑leave事件,触发组件内部方法 -->
  <Transition name="toast" @after-leave="handleAfterLeave">
    <!-- 这里继续用v-show没问题,但是初始visible必须为false -->
    <div v-show="visible" class="momal" :class="typeClass">{{ msg }}</div>
  </Transition>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue';

const props = defineProps(['msg', 'type'])
const emit = defineEmits(['after-leave'])

const typeClass = computed(() => {
  return {
    'momal-success': props.type === 'success',
    'momal-error': props.type === 'error',
    'momal-warning': props.type === 'warning'
  }
})

// 关键:初始为false!不要一开始true
const visible = ref(false)

// 组件挂载完成后,再打开, false → true,触发enter入场动画
onMounted(() => {
  visible.value = true
})

const close = () => {
  visible.value = false
}

const handleAfterLeave = () => {
  emit('after-leave')
}

defineExpose({ close })
</script>

<style>

.momal {
  position: fixed;
  top: 8%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: #ccc;
  padding: 12px 24px;
  max-width: 280px;
  border-radius: 6px;
  z-index: 9999;
  font-size: 14px;
  color:#333;
}
.momal-success {
  background-color: #afffb3;
}
.momal-error {
  background-color: #f56c6c;
  color:#fff;
}
.momal-warning {
  background-color: #ffedb3;
}

/* 入场起始:在目标位置上方 Y=-40%,透明 */
.toast-enter-from {
opacity: 0;
transform: translate(-50%, -80%);
}
.toast-enter-active {
/* 同时过渡透明度 + transform位移 */
transition: all 0.25s ease;
}
/* 入场终点:和momal基础transform完全一致,动画结束无抖动 */
.toast-enter-to {
opacity: 1;
transform: translate(-50%, -50%);
}


.toast-leave-from {
  opacity: 1;
  transform: translate(-50%, -50%) scale(1);
}
.toast-leave-active {
  transition: all 0.25s ease;
}
.toast-leave-to {
  opacity: 0;
  transform: translate(-50%, -80%) scale(1);
}

</style>

二、index.ts

import { h, render } from 'vue'
import Toast from './Toast.vue'

// 属性全部设为可选
interface ToastOptions {
  msg: string
  type?: string
  duration?: number
}

export function toast(options: ToastOptions) {
  const msg = options.msg ?? ''
  const type = options.type ?? ''
  const duration = options.duration ?? 2000

  // 先定义 container,再创建vnode,回调才能捕获变量
  const container = document.createElement('div')
  container.style.pointerEvents = 'none'

  const vnode = h(Toast, {
    msg,
    type,
    onAfterLeave: () => {
      render(null, container)
      container.remove()
    }
  })

  render(vnode, container)
  document.body.appendChild(container)

  setTimeout(() => {
    vnode.component?.exposed?.close()
  }, duration)
}

三、调用

在想使用的页面中导入组件的函数

import { toast } from '@/components/Toast'
// 直接函数调用
toast({ msg: '保存成功' ,type:'success',duration: 2000})

知识点

  1. h, render虚拟dom加载组件的操作
  2. Transition组件内置的事件和样式,主要使用translate和opacity
  3. emit('after-leave')方式抛出事件
  4. vnode如何调用emit事件
  5. 组件中使用v-show实现隐藏和显示
  6. 组件中使用抛出close函数的方式实现visible值的切换
  7. const visible 初始的时候要设置为true,才能实现入场动画
  8. 别忘记绑定@after-leave="handleAfterLeave",在动画结束后销毁dom,节约内存,防止内存泄露
  9. .momal样式需要放在前面,防止transition样式不生效
  10. computed计算样式对象,传入样式绑定

补充:该组件未实现队列功能,在并发情况下,可根据实际需求进行完善。