第五集: 從零開始實現一套pc端vue的ui組件庫toast組件

第五集: 從零開始實現(toast組件)

本集定位:
toast組件的實現, 爭取一章就說完, 更多彈出相關的東西會在alert組件裏面.
下章loading組件.
很開心昨天去現場錄製'脫口秀大會',心情愉悅😁.css

一: toast需求分析 vue

  1. 彈出提示信息的形式出現, 與alert不一樣, toast以簡單提示爲主.
  2. 不用用戶操做dom, 經過js操做便可(好比封裝axios的時候並無template等標籤).
  3. 優先級要可控, 默認要高一些, 畢竟提示要矚目一些.
  4. 可本身消失, 可手動消失.
  5. 很常見的狀況是用戶瘋狂點擊, 或是事件屢次觸發, 處理好多彈出的情況與動畫.
  6. 及時清除dom結構,好比跳轉的時候, 不要形成性能的損耗.

二: 基礎結構的搭建 node

話很少少, 導出文件 Toast/index.js.
導出默認的引入項, 是能夠以下簡寫的.ios

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

// 因爲這個js文件內容比較重要, 咱們放在後面講, 先寫結構
toast.vue文件git

<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部分github

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是一個合格工程的開端.axios

四: 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顏色
提示框須要讓用戶可以很直觀的分辨它的意義, 好比用戶沒有看到提示文字的時候, 但他看到了紅色的提示框, 那他也會知道本身不該該進行剛纔的操做.app

屬性固然就是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
我的網站:連接描述

相關文章
相關標籤/搜索