Vue3 TypeScript 全局 Message 提示框
前言
实现过一个 vue2 + ts 下的全局的 Message 提示框。闲暇之余想在 vue3 + ts 的框架下也实现此功能。
技术思路
在 vue3 上的实现思路
install 函数是把编写的 Message 组件实例化并渲染到页面的关键。其中的步骤:
根据 Message 组件实例化一个 Message
在 document.body 上 appendChild 实例
同时返回一个 destory 函数用于手动销毁实例
倒计时时间到销毁实例
编写 Message 组件代码
// Mssage.vue <template> <transition name="slide"> <div class="message-wrap" :class="[type, center ? 'text-center' : '']" :style="{ ...style }" v-if="visible"> <div v-if="messageArr.length" class="message-line"> <div v-for="(item, index) in messageArr" :key="index" class="message-line-item"> {{ item }} </div> </div> <div v-else class="message">{{ message }}</div> </div> </transition> </template> <script lang="ts"> import { defineComponent } from 'vue'; export default defineComponent({ props: { message: { type: String, default: '', }, type: { type: String, default: 'success', }, duration: { type: Number, default: 2000, }, }, data() { const messageArr: Array<string> = []; const style = {}; return { messageArr, visible: true, center: false, style, }; }, created(): void { const arr = this.message.split('\n'); if (arr.length > 1) { this.messageArr = arr; } }, mounted(): void { this.startTimer(); }, methods: { startTimer(): void { const { duration } = this; const timer = setTimeout(() => { this.visible = false; clearTimeout(timer); }, duration); }, }, }); </script> 复制代码
编写 install 函数并挂载到 vue 全局
// index.ts import { App, render, createVNode } from 'vue'; import Message from './Message.vue'; const defaultOpt = { // 创建默认参数 duration: 2000, type: 'success', }; // 消息数组 const stack: Array<HTMLDivElement> = []; /** * @description: 销毁 body 上的 Message 实例 * @param {HTMLDivElement} ele * @return {*} */ const removeContainer = (ele: HTMLDivElement): void => { const index = stack.findIndex((item) => item === ele); if (~index) { stack.splice(index, 1); setStyle(); } }; /** * @description: 把实例加到实例队列 stack 中,并设置队列中搜有实例的 style * @param {HTMLDivElement} ele * @return {*} */ const addContainer = (ele: HTMLDivElement): void => { stack.push(ele); setStyle(); }; /** * @description: 设置 stack 中所有实例的 style * @param {*} * @return {*} */ const setStyle = () => { stack.forEach((item, index) => { if (item?.getElementsByClassName('message-wrap')?.[0]) { let top = 0; if (index > 0) { top += (stack[index - 1].getElementsByClassName('message-wrap')[0] as HTMLElement)?.getBoundingClientRect() ?.bottom || 0; } // eslint-disable-next-line (item.getElementsByClassName('message-wrap')[0] as HTMLElement).style.marginTop = `${top}px`; } }); }; // 创建挂载实例 // eslint-disable-next-line const createMount = (opts: { [key: string]: any }) => { const { duration } = opts; // 创建一个 div 容器 const container = document.createElement('div'); // 创建 Message 实例,createVNode 的性能比 h 更好 const vm = createVNode(Message, opts); // 把实例 render 到容器上 render(vm, container); addContainer(container); // 把容器渲染到 body 上 document.body.appendChild(container); const destory = () => { const timer = setTimeout(() => { render(null, container); removeContainer(container); document.body.removeChild(container); clearTimeout(timer); }, 500); // 500 为动画结束时间,根据情况修改 }; const timer = setTimeout(() => { destory(); clearTimeout(timer); }, duration || defaultOpt.duration); return { destory }; }; function Toast(options: { message: string; duration?: number } | string): { destory: () => void; } { if (typeof options === 'string') { // eslint-disable-next-line options = { ...defaultOpt, message: options || '', }; } else { // eslint-disable-next-line options = { ...defaultOpt, ...options, }; } return createMount(options); } Toast.install = (app: App<Element>) => { app.component('toast', Message); app.provide('Toast', Toast); // 挂载 Toast 为全局方法 $toast // eslint-disable-next-line app.config.globalProperties.$toast = Toast; }; export default Toast; 复制代码
use 为全局组件;增加 $toast 声明,消除使用时的 ts 报错
import { createApp } from 'vue'; import Toast from '@/components/toast'; const app = createApp(App); app.use(Toast); app.mount('#app'); // 定义了全局方法之后需要扩充类型 declare module '@vue/runtime-core' { interface ComponentCustomProperties { $toast: typeof Toast; } } 复制代码
使用
this.$toast('message'); this.$toast({ message: 'message', dutation: 5000, }) 复制代码
和 vue2 上的对比
vue2 | vue3 | |
---|---|---|
创建方式 | 1. 先创建构造函数 const MessageBox = Vue.extend(MessageComp); 2. 通过构造函数实例化对象 const instance: any = new MessageBox({ data: options, }).$mount(); | 1. 先创建一个 div 容器 const container = document.createElement('div'); 2. createVNode 创建组件实例 const vm = createVNode(Message, opts); 3. 把实例 render 到容器上 render(vm, container); |
tips
作者:codepoet
链接:https://juejin.cn/post/7015514169608765453