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

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

一、Toast.vue

html 复制代码
<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>
.toast-enter-from {
  opacity: 0;
  /* !!! 和momal基础位置保持一致 translate(-50%, -20%),top:20%,垂直偏移是-20%,不要写‑50%,消除位置跳动 */
  transform: translate(-50%, -20%) scale(0.5);
}
.toast-enter-active {
  transition: opacity 0.25s ease, transform 0.25s ease;
}
.toast-enter-to {
  opacity: 1;
  transform: translate(-50%, -20%) scale(1);
}

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

.momal {
  position: fixed;
  top: 20%;
  left: 50%;
  transform: translate(-50%, -20%);
  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;
}
</style>

二、index.ts

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)
}

三、调用

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

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

知识点

h, render虚拟dom加载组件的操作
Transition组件内置的事件和样式
emit('after-leave')方式抛出事件
vnode如何调用emit事件
组件中使用v-show实现隐藏和显示
组件中使用抛出close函数的方式实现visible值的切换
const visible 初始的时候要设置为true,才能实现入场动画
别忘记绑定@after-leave="handleAfterLeave",在动画结束后销毁dom,节约内存,防止内存泄露

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

相关推荐
IT小白杨1 小时前
2026游戏工作室环境隔离指南:从设备指纹到移动IP的部署实践
前端·经验分享·网络协议·tcp/ip·游戏·安全架构·指纹浏览器
用户298698530141 小时前
使用 JavaScript 在 React 中实现 Word 转 PDF
前端·javascript·react.js
hunterandroid1 小时前
[鸿蒙从零到一] @Observed 与 @ObjectLink 深层响应陷阱与最佳实践
前端
hunterandroid1 小时前
[鸿蒙从零到一] ArkTS 装饰器原理与自定义装饰器实践
前端
hunterandroid2 小时前
ContentProvider 跨进程数据共享实战
android·前端
计算机魔术师2 小时前
飞书与豆包合并后首款Agent产品"豆包工作"发布
前端
独孤九剑打醒他2 小时前
从“铁块一直在辐射电磁波“到EUV光源:微波等离子体MPP架构全链路推演
前端·架构·硬件工程
光电的一只菜鸡2 小时前
高通tuning中eis需要调什么
java·开发语言·前端
quweiie3 小时前
腾讯云视频点播-web上传视频
前端·音视频·腾讯云·上传视频·腾讯云视频点播