【Vue3实战】从0到1手把手带你封装一个高颜值 Toast 组件

669 阅读3分钟

Vue3 自定义 Toast 组件封装实践

前言

在前端开发中,Toast 提示是一个非常常用的交互组件。本文将介绍如何在 Vue3 中封装一个功能完整、可复用的 Toast 组件。

功能特点

  • 支持多种提示类型(success/error/warning/default)
  • 自定义显示时长
  • 防抖处理,避免频繁调用
  • 优雅的动画效果
  • 自动清理资源

效果展示:

默认效果: image.png

失败效果: image.png

成功效果: image.png

警告效果: image.png

实现原理

1. 组件结构

Toast 组件采用 Vue3 组合式 API 实现,主要包含三个部分:

  • MyToast.vue:Toast 组件本身
  • toast.js:Toast 插件封装
  • 全局注册和使用

2. 核心代码实现

2.1 Toast 组件 (MyToast.vue)

组件特点:

  • 使用 transition 实现淡入淡出动画
  • 支持不同类型的图标显示
  • 自适应内容宽度
  • 居中显示设计
<template>
  <transition name="fade">
    <div v-if="visible" class="my-toast" :class="type">
      <div class="toast-content">
        <iconpark-icon
          v-if="type !== 'default'"
          :name="iconName"
          size="16px"
          :class="['toast-icon', type]"
          />
        <span>{{ message }}</span>
      </div>
    </div>
  </transition>
</template>
<script setup>
  // 组件逻辑实现
  const props = defineProps({
    message: String,
    type: String,
    duration: Number
  });
  const visible = ref(false);
  let timer = null;
  // 显示方法
  const show = () => {
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
    visible.value = true;
    if (props.duration > 0) {
      timer = setTimeout(() => {
        visible.value = false;
        timer = null;
      }, props.duration);
    }
  };
  // 资源清理
  onBeforeUnmount(() => {
    if (timer) {
      clearTimeout(timer);
      timer = null;
    }
  });
</script>

样式:

<style lang="less" scoped>
.my-toast {
	position: fixed;
	top: 50%;
	left: 50%;
	transform: translate(-50%, -50%);
	z-index: 9999;
	padding: 8px 16px;
	border-radius: 4px;
	background: rgba(0, 0, 0, 0.7);
	color: #fff;
	font-size: 14px;
	line-height: 20px;
	max-width: 80%;

	.toast-content {
		display: flex;
		align-items: center;
		gap: 4px;
	}

	.success {
		color: #52c41a;
	}

	.error {
		color: #ff4d4f;
	}

	.warning {
		color: #faad14;
	}
}

// 动画效果
.fade-enter-active,
.fade-leave-active {
	transition: opacity 0.3s ease;
}

.fade-enter-from,
.fade-leave-to {
	opacity: 0;
}
</style>
2.2 Toast 插件封装 (toast.js)
import { createVNode, render } from 'vue';
import MyToast from '@/components/MyToast.vue';

const toast = {
	install(app) {
		// 创建一个容器
		const container = document.createElement('div');
		document.body.appendChild(container);

		// 用于存储定时器
		let timer = null;

		// 创建显示方法
		const showToast = ({ message = '', type = 'default', duration = 2000 }) => {
			// 如果存在之前的定时器,清除它
			if (timer) {
				clearTimeout(timer);
				timer = null;
			}

			// 确保容器存在
			if (!document.body.contains(container)) {
				document.body.appendChild(container);
			}

			// 清除已有的 toast
			render(null, container);

			// 创建新的 toast
			const vnode = createVNode(MyToast, {
				message,
				type,
				duration,
			});

			// 渲染到容器
			render(vnode, container);

			// 显示 toast
			vnode.component?.exposed?.show();

			// 设置新的定时器
			timer = setTimeout(() => {
				if (document.body.contains(container)) {
					render(null, container);
				}
				timer = null;
			}, duration + 300); // 加300ms确保动画完成
		};

		// 防抖函数
		const debounce = (fn, delay) => {
			let timeoutId;
			return (...args) => {
				clearTimeout(timeoutId);
				timeoutId = setTimeout(() => fn(...args), delay);
			};
		};

		// 注册全局方法
		app.config.globalProperties.$toast = {
			show: debounce((message) => showToast({ message }), 300),
			success: debounce((message) => showToast({ message, type: 'success' }), 300),
			error: debounce((message) => showToast({ message, type: 'error' }), 300),
			warning: debounce((message) => showToast({ message, type: 'warning' }), 300),
		};

		// 在应用卸载时清理
		app.config.globalProperties.$unmounted = () => {
			if (timer) {
				clearTimeout(timer);
				timer = null;
			}
			if (document.body.contains(container)) {
				render(null, container);
				document.body.removeChild(container);
			}
		};
	},
};

export default toast;

3. 关键技术点解析

3.1 组件渲染机制
  • 使用 createVNode 创建虚拟节点
  • 通过 render 函数渲染到 DOM
  • 利用 transition 组件实现动画效果
3.2 防抖处理
// 防抖函数
const debounce = (fn, delay) => {
	let timeoutId;
	return (...args) => {
		clearTimeout(timeoutId);
		timeoutId = setTimeout(() => fn(...args), delay);
	};
};
3.3 资源管理
  • 组件级别的定时器清理
  • 插件级别的容器管理
  • 应用卸载时的资源回收

使用方式

1. 全局注册

import toast from './plugins/toast';
const app = createApp(App);
app.use(toast);

2. 组件中使用

const { proxy } = getCurrentInstance();
// 基础提示
proxy.$toast.show('这是一条提示');
// 成功提示
proxy.$toast.success('操作成功');
// 错误提示
proxy.$toast.error('操作失败');
// 警告提示
proxy.$toast.warning('请注意');

实现要点

1. 单例模式

  • 确保同一时间只显示一个 Toast
  • 新的 Toast 会替换旧的 Toast

2. 防抖处理

  • 避免频繁创建和销毁组件
  • 提升性能和用户体验

3. 资源管理

  • 及时清理定时器
  • 组件卸载时清理 DOM
  • 防止内存泄漏

4. 样式设计

  • 使用 fixed 定位居中显示
  • 添加过渡动画提升体验
  • 支持多种类型的样式定制

优化建议

  1. 性能优化

    • 使用 CSS transform 代替位置属性
    • 合理使用防抖控制调用频率
  2. 功能扩展

    • 支持 HTML 内容
    • 支持自定义位置
    • 支持队列显示
  3. 可访问性

    • 添加 ARIA 属性
    • 支持键盘操作

总结

通过封装 Toast 组件,我们不仅实现了一个实用的提示组件,还学习了:

  • Vue3 组件封装的最佳实践
  • 插件开发和全局注册
  • 性能优化和资源管理
  • 组件通信和状态管理