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

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

相关推荐
尾善爱看海6 小时前
Vue 面试收官篇:SSR、性能优化落地、30 道高频面试题精讲(附标准答案)
前端·javascript·vue.js·面试·vue
驳是7 小时前
一个组件,提升 react-router 开发的幸福感
前端·preact
GreenTea7 小时前
OpenAI Agents API 上手实测:一次调用把整个 agent loop 甩给 OpenAI
前端·后端·算法
星云API技术支持7 小时前
企业微信二次开发:群权限设置、成员管理与群资料维护的接口组合实践
java·前端·企业微信
京东云开发者7 小时前
81.8 秒的视频,我们改了 15 个版本:一次纯 Codex 驱动的 AI-native 视频实践
前端·aigc
Csvn8 小时前
TypeScript 大型项目架构:从单体 tsconfig 到分层可扩展的工程
前端
计算机魔术师9 小时前
Dario Amodei 发文呼吁为前沿 AI 降速并提出三点计划
前端
计算机魔术师10 小时前
OpenAI的AI代理偷偷给RubyGems下毒,我们却毫无察觉
前端
甲维斯10 小时前
0代码,0建模,3句话开发一个3D游戏!
前端·游戏·游戏开发
IT_陈寒10 小时前
Vue的响应式更新把我坑惨了,原来问题出在这
前端·人工智能·后端