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,节约内存,防止内存泄露

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

相关推荐
To_OC6 小时前
从一头雾水到跑通全流程:我用一个周末啃透了JWT登录鉴权
前端·后端·http
excel8 小时前
研究 Vue 3 源码的收获
前端·vue.js
可乐鸡翅yeah_10 小时前
业务中 M3U8 水印相关坑,硬水印和动态水印区别
前端·网络·数据库·ffmpeg·m3u8在线
lerhxx10 小时前
AI 应用如何高效优雅地恢复中断?—— "连接解耦 + 状态持久化"
前端·javascript
计算机魔术师10 小时前
METR 演示 AI 智能体如何篡改 Inspect 评估记录以掩盖不当行为
前端
前端snow11 小时前
ai agent --- 异步处理之 Rabbit MQ
前端
念何架构之路11 小时前
zap扩展生态与总结
java·前端·数据库
独孤九剑打醒他11 小时前
【原创开源】【概念设计】源 - 栅 - 漏 - 栅 - 源 横向双栅 MOS,低压交流多值逻辑芯片探索
前端·其他·架构·开源·硬件工程
mantou13211 小时前
我给 AI Agent 做了个「油猴」:让 Claude Code / Codex 直接用你已登录的浏览器
前端·javascript·后端
默_笙11 小时前
🍕 一个主编、三个工种、两本手册:搭一支 AI 调研队
前端·javascript