前言
实现过一个 vue2 ts 下的全局的 Message 提示框。闲暇之余想在 vue3 ts 的框架下也实现此功能。
技术思路
在 vue3 上的实现思路
install 函数是把编写的 Message 组件实例化并渲染到页面的关键。其中的步骤:
- 根据 Message 组件实例化一个 Message
- 在 document.body 上 appendChild 实例
- 同时返回一个 destory 函数用于手动销毁实例
- 倒计时时间到销毁实例
编写 Message 组件代码
代码语言:javascript复制// 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 全局
代码语言:javascript复制// 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 报错
代码语言:javascript复制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;
}
}
复制代码
使用
代码语言:javascript复制this.$toast('message');
this.$toast({
message: 'message',
dutation: 5000,
})
复制代码
和 vue2 上的对比
代码语言:javascript复制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);