前言
实现过一个 vue2 + ts 下的全局的 Message 提示框。闲暇之余想在 vue3 + ts 的框架下也实现此功能。
技术思路
在 vue3 上的实现思路
install 函数是把编写的 Message 组件实例化并渲染到页面的关键。其中的步骤:
根据 Message 组件实例化一个 Message
在 document.body 上 appendChild 实例
同时返回一个 destory 函数用于手动销毁实例
倒计时时间到销毁实例
编写 Message 组件代码
//Mssage.vue<template><transitionname="slide"><div:class="[type,center?'text-center':'']":style="{...style}"v-if="visible"><divv-if="messageArr.length"><divv-for="(item,index)inmessageArr":key="index">{{item}}</div></div><divv-else>{{message}}</div></div></transition></template><script>import{defineComponent}from'vue';exportdefaultdefineComponent({props:{message:{type:String,default:'',},type:{type:String,default:'success',},duration:{type:Number,default:2000,},},data(){constmessageArr:Array<string>=[];conststyle={};return{messageArr,visible:true,center:false,style,};},created():void{constarr=this.message.split('\n');if(arr.length>1){this.messageArr=arr;}},mounted():void{this.startTimer();},methods:{startTimer():void{const{duration}=this;consttimer=setTimeout(()=>{this.visible=false;clearTimeout(timer);},duration);},},});</script>
编写 install 函数并挂载到 vue 全局
//index.tsimport{App,render,createVNode}from'vue';importMessagefrom'./Message.vue';constdefaultOpt={//创建默认参数duration:2000,type:'success',};//消息数组conststack:Array<HTMLDivElement>=[];/***@description:销毁body上的Message实例*@param{HTMLDivElement}ele*@return{*}*/constremoveContainer=(ele:HTMLDivElement):void=>{constindex=stack.findIndex((item)=>item===ele);if(~index){stack.splice(index,1);setStyle();}};/***@description:把实例加到实例队列stack中,并设置队列中搜有实例的style*@param{HTMLDivElement}ele*@return{*}*/constaddContainer=(ele:HTMLDivElement):void=>{stack.push(ele);setStyle();};/***@description:设置stack中所有实例的style*@param{*}*@return{*}*/constsetStyle=()=>{stack.forEach((item,index)=>{if(item?.getElementsByClassName('message-wrap')?.[0]){lettop=0;if(index>0){top+=(stack[index-1].getElementsByClassName('message-wrap')[0]asHTMLElement)?.getBoundingClientRect()?.bottom||0;}//eslint-disable-next-line(item.getElementsByClassName('message-wrap')[0]asHTMLElement).style.marginTop=`${top}px`;}});};//创建挂载实例//eslint-disable-next-lineconstcreateMount=(opts:{[key:string]:any})=>{const{duration}=opts;//创建一个div容器constcontainer=document.createElement('div');//创建Message实例,createVNode的性能比h更好constvm=createVNode(Message,opts);//把实例render到容器上render(vm,container);addContainer(container);//把容器渲染到body上document.body.appendChild(container);constdestory=()=>{consttimer=setTimeout(()=>{render(null,container);removeContainer(container);document.body.removeChild(container);clearTimeout(timer);},500);//500为动画结束时间,根据情况修改};consttimer=setTimeout(()=>{destory();clearTimeout(timer);},duration||defaultOpt.duration);return{destory};};functionToast(options:{message:string;duration?:number}|string):{destory:()=>void;}{if(typeofoptions==='string'){//eslint-disable-next-lineoptions={...defaultOpt,message:options||'',};}else{//eslint-disable-next-lineoptions={...defaultOpt,...options,};}returncreateMount(options);}Toast.install=(app:App<Element>)=>{app.component('toast',Message);app.provide('Toast',Toast);//挂载Toast为全局方法$toast//eslint-disable-next-lineapp.config.globalProperties.$toast=Toast;};exportdefaultToast;
use 为全局组件;增加 $toast 声明,消除使用时的 ts 报错
import{createApp}from'vue';importToastfrom'@/components/toast';constapp=createApp(App);app.use(Toast);app.mount('#app');//定义了全局方法之后需要扩充类型declaremodule'@vue/runtime-core'{interfaceComponentCustomProperties{$toast:typeofToast;}}
使用
this.$toast('message');this.$toast({message:'message',dutation:5000,})
和 vue2 上的对比
tips
vue2 版本?:https://juejin.cn/post/7015199722805657631