100 行代码实现 vue3 toast 组件

595 阅读1分钟

100 行代码实现 vue3 toast 组件

前言

日常移动端h5开发经常会显示一些简短的提示,方便告知用户一些不是非常重要的信息。例如当接口请求返回一些业务错误信息以及交互上面的提示信息。

很多ui库已经封装好了 Toast 组件,并且也有很多文章写了如何实现,这里分享下vue3 换种简洁的方式实现 toast 组件。

使用方式

直接调用 toast 方法,动态生成 toast弹框, 其中参数满足字符串和对象类型。

test/app.vue

<script lang="ts" setup>
import { toast } from '../lib/index';

const handleShow = () => {
  // toast({
  //   message: `你好aaaa${Math.random()}`,
  //   placement: 'top',
  //   duration: 3000,
  // });
  toast(`${Math.random()}`);
};
</script>

<template>
  <div class="app">
    <button @click="handleShow">弹出toast</button>
  </div>
</template>

<style>
* {
  margin: 0;
  padding: 0;
}
</style>

示例

测试 - Google Chrome 2023-09-05 18-04-07.mp4

分析

  1. 提供动态生成 toast 的方法, 参数满足字符串以及对象类型。
  2. 将 vmdom 渲染到具体节点并且增加响应控制。

最小化实现

lib/toast/index.ts

import { h, ref, render, effect } from 'vue';
import Toast from './index.vue';

let timeId;

// 抽离不同平台 实现 具体 api
export function baseToast(opts, { mountEl }) {
  if (!opts) return;

  let defaultOpts = {
    message: '',
    placement: 'top',
    duration: 3000,
  };

  if (typeof opts === 'string') {
    defaultOpts.message = opts;
  }

  if (typeof opts === 'object') {
    defaultOpts = {
      ...defaultOpts,
      ...opts,
    };
  }

  mountEl && mountEl(defaultOpts);
}

//  web 平台
export function toast(opts) {
  return baseToast(opts, {
    mountEl(a) {
      if (timeId) {
        clearTimeout(timeId);
        timeId = undefined;
      }

      const t = ref(true);
      effect(() => {
        render(
          h(Toast, {
            show: t.value,
            message: a.message,
            placement: a.placement,
          }),
          document.body
        );
      });

      timeId = setTimeout(() => {
        t.value = false;
      }, a.duration);
    },
  });
}

lib/toast/index.vue

<script lang="ts" setup>
const props = defineProps<{
  show: boolean;
  message: string;
  placement: 'top' | 'center';
}>();
</script>

<template>
  <transition name="fade">
    <div v-if="props.show" :class="['toast', placement]">
      {{ props.message }}
    </div>
  </transition>
</template>

<style scoped>
/* vue动画过渡效果设置 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s;
}
.fade-enter,
.fade-leave-to {
  opacity: 0;
}

.toast {
  position: fixed;
  padding: 10px 8px;
  left: 50%;
  background-color: black;
  color: #fff;
  border-radius: 8px;
}

.toast.top {
  top: 50px;
  transform: translateX(-50%);
}
.toast.center {
  top: 50%;
  transform: translate(-50%, -50%);
}
</style>

思考

如果通过js动态生成,最终都绕不开dom api 的操作,也就是命令式编程。原生操作最简单暴力,主要是维护起来不方便。

const el = document.createElement('div');
el.innerHTML = a.message;
el.setAttribute('class', 'toast');
document.body.appendChild(el);
setTimeout(() => {
 document.body.removeChild(el);
}, a.duration);