從零實現Vue的元件庫(十六)- Dropdown 實現

FatGe發表於2019-04-01

概述:Dropdown 下拉選單與 Select 的區別在於它是包含了一部分操作,所以它的 Option 下拉項要對其進行支援。

publish:2019-03-31

自從我寫了關於中後臺系統元件的第一篇文章以來,已經三個月了。由於業務場景的較為簡單,所以它沒有其他元件庫的功能那麼強大,但是整體結構較為類似,程式碼結構清晰,容易擴充套件。

這次是最後一個元件了,之後會整體修改下樣式以及類名,定義下開發規範,希望可以有小夥伴一起來 feature。

將 Dropdown 分為兩個模組,父元件 Dropdown,子元件 Dropdown-option。其中Dropdown 負責控制整體的顯示,Drop-option 負責下拉選單的每一個選項。

從零實現Vue的元件庫(十六)- Dropdown 實現

Dropdown - 元件結構

├── Dropdown

├── Dropdown-option

Dropdown

父元件主要負責的是選中項的顯示以及下拉選單狀態(開、合)的控制。

具體的程式碼如下

<template>
  <div
    :class="['dropdown', { 'is-hover': trigger === 'hover' && isOpen }]"
    tabindex="0"
    @click.stop="isOpen = !isOpen"
    @blur="trigger !== 'hover' && (isOpen = false)"
  >
    <div :class="['dropdown__label']">
      <slot name="label">
        <span class="c-color-success">{{ placeholder }}</span>
      </slot>
      <fat-icon name="expand_more" class="c-color-success" />
    </div>

    <div class="dropdown__menu" v-if="trigger === 'hover' || isOpen">
      <slot name="menu"></slot>
    </div>
  </div>
</template>

<script>
export default {
  name: "dropdown",
  provide() {
    return {
      Dropdown: this
    };
  },
  props: {
    placeholder: { type: String, default: "下拉選單" },
    trigger: { type: String, default: "hover" },
    selectValue: { type: [String, Number] },
    optionKey: { type: String, default: "value" }
  },
  data() {
    return {
      isOpen: this.trigger === "hover",
      selectItem: {}
    };
  },
  model: {
    prop: "selectValue",
    event: "select"
  }
};
複製程式碼

首先對處理下拉選單開關狀態的控制,依據 trigger 也就是觸發方式的不同,可以分為兩類 hover || click

  • trigger = 'hover' 時,對最外層的 div 新增 is-hover 的類名,它主要是負責新增 :hover 偽類來顯示下拉選單
    &.is-hover {
        &:hover {
            .dropdown__menu {
                display: block;
            }
        }
        .dropdown__menu {
            display: none;
        }
    }
複製程式碼

​ 同時依據 trigger 初始化 isOpen 狀態為 true

  • trigger = 'click' 時,利用 isOpen 的狀態來控制下拉選單的開、合。主要是依據事件來觸發,利用 @click.stop="isOpen = !isOpen",來完成下來選單的展開操作。之後,對最外層的 div 新增 tabindex="0" 屬性使得它能夠觸發失焦事件 blur,同時新增 @blur="trigger !== 'hover' && (isOpen = false)",意味著當它失效的時候,自動關閉下拉選單。

以上完成了 Dropdown 對下拉選單控制的功能,利用 provide ,完成它與 Dropdown-option 的通訊,傳遞 selectValueselectItemoptionKey

Dropdown-option

Dropdown-option 是下拉選單的每個選項,其基本結構

<template>
  <div
    :class="[
      'dorpdown-option',
      { 'is-disabled': disabled },
      { 'is-selected': isSelected }
    ]"
    @mousedown="handleClick"
  >
    <slot>
      {{ label }}
    </slot>
  </div>
</template>
複製程式碼

主要是利用預設插槽,和 label 屬性來構建每一項,並且包含著兩種狀態,是否 disabled 或 selected。disabled 狀態是依據 props: disabled 來改變的,而 selected 則是由 computed 來完成的

<script>
export default {
  inject: {
    Dropdown: { default: "Dropdown" }
  },
  computed: {
    isSelected() {
      const {
        Dropdown: { optionKey, selectValue }
      } = this;
      const key = this[optionKey] || this.$attrs[optionKey];

      return key === selectValue;
    }
  },
  ...
};
</script>
複製程式碼

首先利用 inject 將父元件 Dropdown 注入,這樣可以通過 this.Dropdown 來訪問它的狀態、屬性。

然後在 isSelected() 中獲取 selectValue,與當前 Dropdown-option 的 key 值進行比對,檢視是否為選中項。

為了要引入 optionKey,是因為在實際的業務中,有的場景會以 label 作為去區分項,有的則是以 value,故引入,方便自定義。

每個 Dropdown-option 具備選中功能,但是從 @mousedown="handleClick" 可以看出,利用 mousedown 來代替 click 事件

由於我們利用 Dropdown 的 blur 事件來控制下拉選單的展開與關閉,此時如果利用 click 事件,則會在 blur 之後觸發,所以無法選中。故採用 mousedown 來完成該功能。

methods: {
    handleSelect(key) {
        let {
            Dropdown: { multiple, trigger },
            value,
            label
        } = this;

        this.Dropdown.$emit("change", key);
        this.Dropdown.$emit("select", key);
        if (trigger !== "hover") {
            this.Dropdown.isOpen = false;
        }
    },
        handleClick() {
            let {
                Dropdown: { optionKey },
                disabled
            } = this;
            const key = this[optionKey] || this.$attrs[optionKey];

            if (!disabled) {
                this.$slots.default[0].elm.click && this.$slots.default[0].elm.click();
                key && this.handleSelect(key);
            }
        }
}
複製程式碼

這一份部分的邏輯就比較簡單了,只有一處需要解釋下

this.$slots.default[0].elm.click && this.$slots.default[0].elm.click();
複製程式碼

由於我們利用 mousedown 來代替原來的 click 事件,但我們利用 slot 插槽來完成下拉選單的開發時,就無法觸發 slot 的點選時間,所以利用上述程式碼來手動觸發。

由於 Dropdown 元件中,使用了 v-model 來完成資料的雙向繫結

model: {
    prop: "selectValue",
    event: "select"
}
複製程式碼

所以在 Dropdown-option 中則需要利用 this.Dropdown.$emit("select", key); 來完成雙向繫結。

總結

程式碼地址:Dropdown Github

例項:Fat-UI lib

相關文章