阅读 370

Vue3 TypeScript 全局 Message 提示框

前言

实现过一个 vue2 + ts 下的全局的 Message 提示框。闲暇之余想在 vue3 + ts 的框架下也实现此功能。

技术思路

总思路.png

在 vue3 上的实现思路

流程图.png

install 函数是把编写的 Message 组件实例化并渲染到页面的关键。其中的步骤:

  1. 根据 Message 组件实例化一个 Message

  2. 在 document.body 上 appendChild 实例

  3. 同时返回一个 destory 函数用于手动销毁实例

  4. 倒计时时间到销毁实例

编写 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 上的对比


vue2vue3
创建方式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

文章分类
后端
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐