preface

Implementation of a vue2 + TS global Message prompt box. In my spare time, I want to realize this function under the framework of VUE3 + TS.

Technical approaches

The realization idea in VUE3

The install function is key to instantiating and rendering the Message component you wrote to the page. The steps are as follows:

  1. Instantiate a Message based on the Message component
  2. AppendChild instance on document.body
  3. A destory function is also returned to manually destroy the instance
  4. Countdown time to destroy the instance

Write the Message component code

// 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>
Copy the code

Write the install function and mount it globally to vue

// index.ts
import { App, render, createVNode } from 'vue';
import Message from './Message.vue';

const defaultOpt = {
  // Create default parameters
  duration: 2000.type: 'success'};// Message array
const stack: Array<HTMLDivElement> = [];

/ * * *@description: Destroys the Message instance * on body@param {HTMLDivElement} ele
 * @return {*}* /
const removeContainer = (ele: HTMLDivElement): void= > {
  const index = stack.findIndex((item) = > item === ele);
  if (~index) {
    stack.splice(index, 1); setStyle(); }};/ * * *@descriptionAdd the instance to the stack and set the style * for the instance in the queue@param {HTMLDivElement} ele
 * @return {*}* /
const addContainer = (ele: HTMLDivElement): void= > {
  stack.push(ele);
  setStyle();
};
/ * * *@description: Sets the style * of all instances in the stack@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] asHTMLElement)? .getBoundingClientRect() ? .bottom ||0;
      }
      // eslint-disable-next-line
      (item.getElementsByClassName('message-wrap') [0] as HTMLElement).style.marginTop = `${top}px`; }}); };// Create a mount instance
// eslint-disable-next-line
const createMount = (opts: { [key: string]: any }) = > {
  const { duration } = opts;
  // Create a div container
  const container = document.createElement('div');
  CreateVNode has better performance than H
  const vm = createVNode(Message, opts);
  // Render the instance to the container
  render(vm, container);
  addContainer(container);
  // Render the container onto the body
  document.body.appendChild(container);
  const destory = () = > {
    const timer = setTimeout(() = > {
      render(null, container);
      removeContainer(container);
      document.body.removeChild(container);
      clearTimeout(timer);
    }, 500); // 500 is the end time of animation, modified according to the situation
  };

  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-lineoptions = { ... defaultOpt,message: options || ' '}; }else {
    // eslint-disable-next-lineoptions = { ... defaultOpt, ... options, }; }return createMount(options);
}

Toast.install = (app: App<Element>) = > {
  app.component('toast', Message);
  app.provide('Toast', Toast);
  // Mount Toast as the global method $Toast
  // eslint-disable-next-line
  app.config.globalProperties.$toast = Toast;
};

export default Toast;
Copy the code

Use is a global component; – Added $toast declaration to eliminate ts error when using

import { createApp } from 'vue';
import Toast from '@/components/toast';

const app = createApp(App);
app.use(Toast);
app.mount('#app');

// After defining the global method, you need to extend the type
declare module '@vue/runtime-core' {
  interface ComponentCustomProperties {
    $toast: typeofToast; }}Copy the code

use

this.$toast('message');

this.$toast({
  message: 'message'.dutation: 5000,})Copy the code

Compared to vue2

vue2 vue3
Create a way Const MessageBox = vue.extend (MessageComp);

Const instance: any = new MessageBox({data: options,}).$mount();
Const container = document.createElement(‘div’);

CreateVNode Creates a component instance const VM = createVNode(Message, opts);

Render (vm, container);

tips

Vue2 version 🔗 : juejin.cn/post/701519…