乐趣区

第五集-从零开始实现一套pc端vue的ui组件库toast组件

第五集: 从零开始实现 (toast 组件)

本集定位:
toast 组件的实现, 争取一章就说完, 更多弹出相关的东西会在 alert 组件里面.
下章 loading 组件.
很开心昨天去现场录制 ’ 脱口秀大会 ’, 心情愉悦????.

一: toast 需求分析

  1. 弹出提示信息的形式出现, 与 alert 不同, toast 以简单提示为主.
  2. 不用用户操作 dom, 通过 js 操作即可 (比如封装 axios 的时候并没有 template 等标签).
  3. 优先级要可控, 默认要高一些, 毕竟提示要瞩目一些.
  4. 可自己消失, 可手动消失.
  5. 很常见的情况是用户疯狂点击, 或是事件多次触发, 处理好多弹出的状况与动画.
  6. 及时清除 dom 结构, 比如跳转的时候, 不要造成性能的损耗.

二: 基础结构的搭建

话不多少, 导出文件 Toast/index.js.
导出默认的引入项, 是可以如下简写的.

export {default} from './main/toast.js';

// 由于这个 js 文件内容比较重要, 我们放在后面讲, 先写结构
toast.vue 文件

<template>
// toast 弹框没有动画的话就太寒掺了.
// 我这边定义的是一个从上到下掉出来的效果. 
  <transition name="cc-toast-fade">
// 命名规则还是老样子, bem 原则.
    <div class="cc-toast"
    // 上面提到的 z-index 属性, 对于这种组件来说, 用户必须可控.
         :style="'zIndex:'+zIndex">
         // 用户可以随意插入内容
         // 写成变量形式也可以, 这里用 slot 是为了见名知意.
      <slot />
      // 下面的是关闭的 'X' icon, 
       <i class="cc-toast__closeButton"
         @click="deleteEl"
         v-if="showButton">
        <ccIcon name='cc-close'/>
      </i>
    </div>
  </transition>
</template>

js 部分

import ccIcon from "@/components/Icon/main/icon.vue";
export default {
  name: "toast",
  components: {ccIcon},
  props: {
     zIndex: {
      type: Number,
      default: 10
     },
     showButton: {
      // 是否要关闭的 x
      type: Boolean,
      default: true
    },
   }
}

三: 本人 z -index 的一些看法
从 z -index 的数值设定上就能看出一个人的做事风格, 比如我之前工作时候带过的几个人, 我甚至看到有些人经常写 999999 , 我不让他这样写, 之后他还跟我反应说, 不这样写不习惯????‍♀️, 我说如果要写比他等级更高的怎么办? 他说: 肯定不可能有那种情况.
其综合分析也就明白了, 喜欢这样去书写的人,
一是没有太长远的规划, 没有想到未来的多样性,
二是并没有为团队配合而考虑, 没有为他人书写代码 ’ 留有余地 ’ 而只顾自己.

本人独立负责的实际项目中试验过, 1-10 的 z -index 已经可以应付绝大多数工程了, 一个中小型项目分成 10 层真的算很多了, 这里面也有一些技巧, 比如说, 每次设置的时候要设置 1, 3, 5, 7, 9, 这是为了如果以后有特殊情况要放到某两个中间的, 可以留一个插入位, 随意约束好 z -index 是一个合格工程的开端.

四: scss 基本样式的设定
vue-cc-ui/src/style/Toast.scss

@import './common/mixin.scss';
@import "./common/animation.scss";

@include b(toast) {
    position: fixed;
    display: inline-block;
    background-color: white;
    top: 6px;
    // 上方, 居中
    left: 50%;
    padding: 6px 20px;
    transition: all .3s;
    transform: translateX(-50%);
    // 本套 ui 的精髓???? 黑边.
    @include commonShadow($--color-black);
    // 定义了动画的样子
    @at-root {@include commonType(cc-toast--);
        .#{$namespace}-toast--big {padding: 6px 35px 6px 20px;}
        .#{$namespace}-toast-fade-enter,
        .#{$namespace}-toast-fade-leave-active {
            opacity: 0;
            transform: translate(-50%, -100%);
        }
    };
}

五: 提示框的 type 颜色
提示框需要让用户能够很直观的分辨它的意义, 比如用户没有看到提示文字的时候, 但他看到了红色的提示框, 那他也会知道自己不应该进行刚才的操作.

属性当然就是 type 了

 <div class="cc-toast"
     :class="[toastType]" // 这里接收
     :style="'zIndex:'+zIndex">
 props: {
    type: {
      type: String,
      default: "nomal"
    },

计算属性里面我们还要规定一些合法值, 以及用户传错了的时候的默认值.

computed: {toastType() {
    // 这里我做了 正常, 成功, 失败, 警告
      let ary = ["nomal", "success", "warning", "danger"];
      // 这里写三元也可以, 但不方便扩展, 万一以后要改那,,,,
      // 其实无所谓, 我只是不想写那种长长的代码段
      if (ary.includes(this.type)) {return "cc-toast--" + this.type;}
      return "cc-toast--nomal";
    }
  },

scss

  @at-root {@include commonType(cc-toast--);
    };

之前写的 util 类, 循环加上彩色边框

@mixin commonType($name) {@each $type in (success, warning, danger) {.#{$name}#{$type} {@include commonShadow($type);
    }
  }
}

效果展示




6: 渲染到页面上 (说了这个才能引出如何关闭它)

vue-cc-ui/src/components/Toast/main/toast.js
这里我会写很详细的注释!!!

// 1: 把写好的组件引进来, 根据打包的规则, 其实引得已经是处理好的 js 代码了
import toast from './toast.vue';
export default {
// 2: 配合 use 方法
  install(Vue) {
// 3: 为了保证全局都能控制弹出 toast, 我们只能原型编程
// 这个 options 就是将来我们调用他的时候, 传的参数.
    Vue.prototype.$ccToast = function(options) {
    // 4: 使用基础 Vue 构造器,创建一个“子类”。let Constructor = Vue.extend(toast),node;
      // 5: 我们传入了配置项
      if (typeof options === 'object' && options instanceof Object) {
      // 6: 实例化这个组件
        node = new Constructor({
        // 7: 相当于你在定义 data 里面的数据
          propsData: options
        });
        // 8: 提取需要弹出的信息到默认插槽
        // 这里为什么要用数组我解释一下
        // 因为这里我们传入的是 '结构'
        // 所以他可能多个, 也可能是嵌套,
        // 所以 vue 分析它必须要用数组的形式
        // 也可以说他要的是 '子元素的集合'.
        // 这方面有兴趣可以去看 render 的实现.
        node.$slots.default = [options.message];
         // 5: 我们传入一串字符串
      } else if (typeof options === 'string') {
      // 6: 直接实例化即可
        node = new Constructor();
        node.$slots.default = [options];
      }
      // 9: 调用函数的生命周期
      node.vm = node.$mount();
      // 10: 让他显示出出来
      node.vm.visible = true
      // 11: 无情插入
      // 如果有特殊需求的同学, 不想让他插入 body, 而是通过传入来定也是不错的.
      document.body.appendChild(node.$el);
    };
  }
};

7: 清除组件, 也可以说是关闭

  1. 用户点击 x 之后, 需要移除这个 dom 结构而不是隐藏
  2. 用户设置了不显示 x 则需要倒计时关闭
  3. 元素采用后者遮盖前者的方式显示.
  4. 思前想后没有定义 clearAll 这个函数, 原因如下:
    之前我做的工程总是出现这样的问题, 比如一个人很谨慎, 他在写完一个提示框之后, 会写一个 clearAll(), 把所有的提示都清除, 这有涉及到了工程化, 组件定义的这个 clearAll 会无差别的清除所有显示的, 导致他清除了别人的提示信息, 而这种 bug 并不容易发现, 不是很喜欢这种太绝对函数.

x 绑定 click 事件.

<i class="cc-toast__closeButton"
         @click="deleteEl"
         v-if="showButton">
        <ccIcon name='cc-close'/>
      </i>
// 专门负责清理工作的函数
deleteEl() {
// 先触发动画效果, 不要直接 rm, 不然效果会很差.
      this.visible = false;
      setTimeout(() => {
      // 下这样才能清理干净一个组件????
       // 这里是去除元素
        this.$el.remove();
       // 完全销毁一个实例。清理它与其它实例的连接,解绑它的全部指令及事件监听器。this.$destroy();}, 300);
    },

scss

// 这个 'x' 还需要调节一下样式,  
// 并且浪一下, 赋予 hover 时候疯狂旋转的动画, 充满朝气
.#{$namespace}-toast__closeButton {
        display: flex;
        cursor: pointer;
        position: absolute;
        align-items: center;
        justify-content: center;
        top: 0;
        right: 0;
        bottom: 0;
        padding: 0 5px;
        &:hover {animation: rotating .5s infinite linear;}
    }

用户设置了自动关闭, 过期时间,

  props: {
    closeTime: {
          // 关闭的延时
          type: Number,
          default: 2000
        },
    }
// 初始化是否设置了时间的操作
  initAutoClose() {
// 万一用户传了个负数, 还是替他 abs 一下吧.
      if (this.autoClose && Math.abs(this.closeTime) > 0) {
      // 这里要用变量接一下, 方便停止他
        this.timer = setTimeout(() => {this.deleteEl();
        }, Math.abs(this.closeTime));
      }
    },  

用户想详细看看提示内容, 把鼠标放在了 toast 上, 并不点击关闭

<div class="cc-toast"
         :class="[
         toastType,{'cc-toast--big':showButton}]":style="'zIndex:'+zIndex"
         // 移入进来就别关闭倒计时了
         @mousemove="clearTimer"
         // 移出就从新走一遍倒计时初始化
         @mouseleave="initAutoClose"
         v-show="visible">
clearTimer() {clearTimeout(this.timer);
    },

用户想点击键盘的 esc 键, 关闭这个组件

mounted() {this.initAutoClose();
    // 开始就要添加对用户键盘事件的监听
    document.addEventListener("keydown", this.keydown);
  },
keydown(e) {if (e.keyCode === 27) {this.deleteEl();
      }
    }

// 既然有 document 的操作, 当然就要有去除的操作.

 beforeDestroy() {
      // 万一用户写了个很长的时间, 虽然 element 没有对此进行处理, 但我还是想写一下.
      this.clearTimer()
      document.removeEventListener('keydown', this.keydown);
    }

end 到这里为止, 一个健康的 toast 也就可以正常使用了

感谢您的阅读, 喜欢的话就收藏吧, 让我们一起终生学习.

下章 loading 组件.

github:github
个人网站: 链接描述

退出移动版