第十集: 從零開始實現一套pc端vue的ui組件庫( 計數器組件 )

第十集: 從零開始實現( 計數器組件 )

本集定位:
聽到計數器這個名字不少人是否是一瞬間沒有什麼印象, 畢竟這個組件用的比較少,就是那種左邊一個'-'右邊一個'+', 控制某些數量的時候纔會用到, 好比我以前作的商城小程序只有'下單'頁面的規格彈出框裏面纔有他的身影, 若是是涉及處處理商品數量很頻繁的業務場景應該會很常見吧, 可是不要看這個組件小, 編寫它的時候坑還很多, 本次咱們就來作一個計數器, 目標就是儘量小, 儘量的省性能.css

1:需求分析

  1. 每次+1 -1是常態, 可是若是搞活動, 每次最少爲+-2個或三個, 就要兼容一下了,( 舉一個實際遇到的坑, 咱們以前把用戶限制爲每次活動, 每一個用戶只能買2個, 可是沒有作好防備, 致使用戶可能此次只買一個, 而下次他再次購買的時候會提示每次只能買兩個, 但顯示他只點擊了買一個, 由於他已經買過一個, 爲了兼容這個問題, 搞得還要加莫名其妙的補救代碼 )
  2. 中間的顯示區應該可輸入的, 用戶想買1000個不可能讓他+1+1+1..., 某些組件採用的是, 平時其爲div, 點擊以後變爲input, 我的感受徹底不必, 一個元素就夠了, 何須搞兩個元素, input狀態下把他的默認樣式去掉就行了.
  3. 左右兩邊要有限制, 不少時候會有限購一說, 好比我作的商城, 庫存只有10個 或者單個用戶最多購買3個, 最少買兩個等等限制.
  4. 小數位數的顯示一說... 這個其實我還真遇到過, 有一種需求叫作, 只要涉及數字就必須精確到後兩位, 這種需求會致使後臺同窗對數據庫作必定的限制, 從而咱們傳給後臺的數據也就存在限制了.

2: 基本結構:

先展現一章普通狀態的圖, 讓咱們更直觀的去完成它, 造型比較別緻, 是本套組件的一個特色, 哈哈作的與別人同樣會致使思想的禁錮, 本身寫代碼多嘗試新的東西, 可是工做中必定要中規中矩, 以公司條款爲準則.
圖片描述前端

vue-cc-ui/src/components/InputNumber/index.jsvue

import inputNumber from './main/input-number.vue'

inputNumber.install = function(Vue) {
  Vue.component(inputNumber.name, inputNumber);
};

export default inputNumber

vue-cc-ui/src/components/InputNumber/main/input-number.vuegit

<template>
  <div class="cc-input-number">
    // 左側的'➖'符號
    <div class="cc-input-number__reduce">
      // 本身封裝的icon組件, 出鏡率還挺高😼.
        <cc-icon name='cc-reduce2'/>
    </div>
    // 中間的顯示與輸入部分,讓人又愛又恨的number屬性
    // 下面的屬性就能幹掉凡人的上下按鈕
    // input::-webkit-outer-spin-button,
    // input::-webkit-inner-spin-button {
    // -webkit-appearance: none;
    // }
    <input ref="input"
           type="number"
           class="cc-input-number__input">
    <div class="cc-input-number__add">
        <cc-icon name='cc-add2'/>
    </div>
  </div>
</template>

這裏咱們選擇吧input與button放在一個div裏面, 且同級別這種方式, 與其餘的不太同樣, 由於這樣更直觀, 並且也足夠實現我想要的功能.github

3: 事件的綁定

// 減小
 <div class="cc-input-number__reduce"
         @click='reduce'>
 // 增長
<div class="cc-input-number__add"
         @click="add">
// 輸入框的監控
<input ref="input"
       type="number"
       class="cc-input-number__input"
       @input="inputChange($event)">

這裏咱們有個問題, 就是本組件採用的是v-model的形式編寫, v-model有一些弊端, 在測試的時候我發現, 好比說用戶爲多個組件綁定了相同的v-model會致使無限渲染的bug, 下面會解讀解決這類bug的相關代碼.web

prpos數據庫

props: {
    max: { type: Number }, // 數字不傳默認是undefined
    min: { type: Number },
    step: { // 每次計算的單位
      type: Number,
      default: 1
    },
    value: { 
    // 綁定的數值, 這裏容許兩種type, 爲了方便用戶書寫,具體判斷下面咱們本身寫
      type: [String, Number],
      required: true
    },
    precision: { // 顯示小數點後幾位數
      type: Number,
      validator(value) {
        if (value < 1 || value === undefined) {
          return 1;
        } else {
          return parseInt(value);
        }
      }
    }
  },

add 方法的實現小程序

add() {
// 極可能用戶就輸入了一個string屬性, 
// 1: 好比後臺返回的就是字符串;
// 2: input框輸入的就是字符串類型;
// 3: 用v-model綁定了一樣的值的其餘組件賦予了這個值string類型;
  let num = Number(this.value) + this.step; // 加上固定的長度
// 這裏咱們抽象出一個專門負責數值的變化的函數
  this.emitVal(num);
},

reduce 方法的實現app

reduce() {
      let num = Number(this.value) - this.step;
      this.emitVal(num);
    },

監聽input框的輸入事件dom

inputChange(e) {
// 這裏就有可能出現string類型的了
      this.emitVal(Number(e.target.value));
    },

關鍵性的賦值函數
emitVal

emitVal(newVal) {
      let { max, min } = this;
      // 不傳參數的時候默認值就是undefined
      // 對這個值的限制就是, max以內, min以上
      if (max !== undefined && newVal > max) newVal = max;
      if (min !== undefined && newVal < min) newVal = min;
      // 這裏兼容一下位數控制
      let value = Number(newVal).toFixed(this.precision);
      // 這個oldVal下面會解釋👇
      if (value === this.oldVal) return;
      this.oldVal = ls;
      // 發出兩個事件, 一個負責改變value, 一個負責返回給用戶
      // 畢竟用戶不可能監聽input事件而後再把值附上去, 太麻煩
      this.$emit("input", value);
      this.$emit("change", value);
      // 這一步很重要
      // 下面會詳細說
      this.$refs.input.value = value;
    }

上面遺留的問題,這裏解釋一下.

  1. oldVal: 能防止不少多餘的改變, 好比說用戶複製粘貼了一組數進來, 這個數大於max, 可是當時顯示的數值就是max, 因此就不用渲染了, 或者v-model不止綁定了這個組件, 還綁定了其餘各類組件, 致使值超出範圍, 這邊也會進行相應的限制, 而這個oldVal 就是上一個合法的值, 因此在作完檢測以後, 檢測經過的數值要賦值給他.
  2. this.$refs.input.value = value; 這一步看似很沒用, 由於輸入框裏面的是value, value改變input裏面的值天然會改變, 可是實際測試並非這樣, 問題也是出如今v-model上, 綁定不少的時候會出現值的不改變, 多是vue的機制問題, 並且他要放在 this.$emit(....);下面操做, 若是放在上面會致使屢次執行, 由於他的執行會循環觸發input的監聽事件, 屢次試驗以後, 仍是放在這裏沒有bug.

上面的兩個問題都是涉及到v-model的問題, 下面還有一個同類的問題, 咱們來看看.
對value進行的監控
由於value的變化, 不必定全是 經過+-輸入這三種方式, 還有第三方經過v-model的方式, 還有用戶手動亂填的方式.

watch: {
    value: {
      handler() {
        // 爲了解決, 多組件共同v-model採用的這個方法, 也算是另闢蹊徑了
        let { value, time } = this;
        clearTimeout(time);
        // 畢竟把它放入宏任務Macrotasks能夠躲過不少無限循環.
        time = setTimeout(() => {
          if (value !== undefined) this.emitVal(value);
        });
      },
      // 這個是開啓進頁面的瞬間就出發一次的意思, 頗有用, 可是數據稍大會消耗性能, 慎用
      // watch還有一個deep屬性, 更是吃性能吃的厲害, 能夠深度監控裏面的數據
      immediate: true
    }
  }

上面的問題都是基於v-model的, 因此很早就有人剔除雙向綁定的壞處, 封裝越多的組件感受就越明顯.

4: 關於樣式的斷定

在計算屬性裏面咱們隊當前值進行了監控, 返回的是置灰的顏色, 這個讓用戶自定的意義不大, 因此直接寫了.

computed: {
    valueMin() {
      if (this.value === this.min) return "#bbbbbb";
      return "";
    },
    valueMax() {
      if (this.value === this.max) return "#bbbbbb";
      return "";
    }
  },

dom, 點擊到了最大值的話就會置灰, 咱們上面已經阻止了繼續點擊的渲染

<cc-icon size='25px'
         name='cc-add2'
         :color="valueMax" />

作點有意思的事
slot是個自由度很高的標籤
把左右按鈕都包上, 讓用戶能夠本身定義顯示的標籤是什麼樣子的

<div class="cc-input-number__reduce"
         @click='reduce'>
      <slot name='left'>
        <cc-icon size='25px'
                 name='cc-reduce2'
                 :color='valueMin' />
      </slot>
    </div>

vue-cc-ui/src/style/inputNumber.scss

@import './common/var.scss';
@import './common/extend.scss';
@import './common/mixin.scss';
@import './config/index.scss';

@include b(input-number) {
// 友好的小手
    cursor: pointer;
    // 有個放大動畫, 看過我文章的同窗都知道, 操做類的組件, 我喜歡有一個懸停放大效果.
    transition:all .1s;
    align-items: center;
    display: inline-flex;
    background-color: white;
    &:hover {
    // 放大被其餘組件擋住就划不來了
      z-index: 6;
      transform: scale(1.2);
    }
    // 招牌陰影
    @include commonShadow($--color-black);
    @include e(add) {
        @include flexCenter();
        padding: 4px 6px;
    }
    @include e(reduce) {
        @include flexCenter();
        padding: 4px 6px;
    }
    @include e(input) {
    // 去掉輸入框的默認樣式
        border: none;
        outline:none;
        display: block;
        text-align: center;
        width:60px;
        height: 20px;
    }
}

效果展現

圖片描述
圖片描述

end
總的來講是這些組件中比較簡單的一個了, 有些坑可以讓我更好的學習vue以及前端的思想, 總的來講挺有趣的.
你們繼續一塊兒學習,一塊兒進步, 早日實現自我價值!!
下一集準備聊聊 tab切換組件的相關知識;
github:連接描述
我的博客: 連接描述

相關文章
相關標籤/搜索