Select 組件實現

當選項過多時,使用下拉菜單展現並選擇內容。javascript

Select 組件主要特色在於:
  • 數據雙向綁定,下拉列表變更時,選中項如何回顯;
  • 單選、多選的區分,以及對應處理。

1. 實例

最終效果

代碼html

<fat-select v-model="inputValue">
    <fat-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" >{{ item.label }}</fat-option>
</fat-select>
複製代碼

實例地址:Select 實例java

代碼地址:Github UI-Librarygit

2. 原理

Select 組件的基本結構以下github

最終效果

主要可分爲兩個部分:app

  • 顯示框:用來展現已經選中項,包含取消按鈕;
  • 下拉框:包含已選中的高亮項,禁用項,默認選擇選項等,具有點擊選中,再次點擊取消的操做。

fat-select 顯示框ide

<template>
    <div :class="['select-wrapper', { 'is-disabled': disabled }]" tabindex="0" @click.stop="isOpen = !disabled && !isOpen" @blur="handleBlur" >
      <div class="select-top-part">
        <template v-if="!selectItems.length">
          <span class="placeholder">{{ placeholder }}</span>
        </template>

        <template v-else>
          <div>{{ selectItems[0].label }}</div>
        </template>
      </div>
      <!-- 下拉框 -->
      <div class="select-bottom-part" v-show="isOpen">
        <slot></slot>
      </div>
    </div>
</template>

<script> export default { props: { placeholder: { type: String, default: "請選擇" }, optionKey: { type: String, default: "value" }, value: { type: [String, Object, Number, Array] } }, model: { prop: "value", event: "input" }, data() { return { isOpen: false, selectValue: [], selectItems: [] }; }, provide() { return { fatSelect: this }; }, watch: { value: { handler(value) { const { multiple } = this; const init = value ? value : multiple ? [] : ""; this.selectValue = multiple ? [...init] : init; }, immediate: true }, selectValue: { handler(value) { this.selectItems = []; } } }, methods: { handleDelete(item) { const { value } = item; this.selectValue = this.selectValue.filter(item => item !== value); this.$emit("input", this.selectValue); this.$emit("change", this.selectValue); }, handleBlur(event) { this.isOpen = false; this.$emit('blur', event); } ... } }; </script>
複製代碼

利用 tabIndex 屬性使得最外層的 div 可以觸發 blur 事件,若是失焦就收起下拉框。ui

<div
    :class="['select-wrapper', { 'is-disabled': disabled }]"
    tabindex="0"
    @click.stop="isOpen = !disabled && !isOpen"
    @blur="handleBlur"
>
    ...
    <!-- 下拉框 -->
    <div class="select-bottom-part" v-show="isOpen">
        <slot></slot>
    </div>
</div>

handleBlur(event) {
    this.isOpen = false;
    this.$emit('blur', event);
}
複製代碼

組件實現數據雙向綁定,當 v-model 對應的值變更時,Select 組件的值也會發生改變,可是顯示框內所呈現的是選中項的 label 屬性,因此將選中值 selectValue 和選中項 selectItems 進行區分。this

同時配置 v-model 相關屬性,同時監測 watch 相關 value 具體以下spa

model: {
    prop: "value",
    event: "input"
},
watch: {
    value: {
        handler(value) {
            const { multiple } = this;
            const init = value ? value : multiple ? [] : "";
            this.selectValue = multiple ? [...init] : init;
        },
        immediate: true
    }
}
複製代碼

同時利用 provide 向其全部下拉框注入一個依賴,用於訪問 selectValueselectItemspropdata

provide() {
    return {
        fatSelect: this
    };
}
複製代碼

默認 optionKey: { type: String, default: "value" } 做爲下拉項的惟一標識,默認值爲 value ,也可自定義。

fat-option 下拉框

利用插槽將下拉框插入 Select 組件中,其具體定義以下

<template>
  <div :class="['select-option-wrapper', { 'is-selected': isSelect }, { 'is-disabled': disabled }]" @click.stop="handleClick" >
    <slot></slot>
  </div>
</template>
<script> export default { props: { value: { type: [Object, String, Number], required: true }, label: { type: String }, disabled: { type: Boolean, defa: false } }, inject: ["fatSelect"], computed: { isSelect() { const { fatSelect: { optionKey, selectItems } } = this; const key = this[optionKey] || this.$attrs[optionKey]; return selectItems.find(item => item.key === key); } }, watch: { ["fatSelect.selectValue"]: { handler(newValue) { const { value, label, fatSelect: { optionKey, multiple, selectValue } } = this; const key = this[optionKey] || this.$attrs[optionKey]; if ( newValue === value || (Array.isArray(newValue) && newValue.find(item => item === value)) ) { if (!multiple) { this.fatSelect.selectItems = [ { key, label, value } ]; } else { this.fatSelect.selectItems.push({ key, label, value }); } } }, immediate: true } }, methods: { ... } }; </script>
複製代碼

利用 inject: ["fatSelect"] 將上述 provideSelect 組件注入到當前選項中,

經過 this.fatSelect 來訪問父組件的 selectItems 來判斷,當前選項是否爲選中項。

isSelect() {
    const {
        fatSelect: { optionKey, selectItems }
    } = this;
    const key = this[optionKey] || this.$attrs[optionKey];

    return selectItems.find(item => item.key === key);
}
複製代碼

同時watch fatSelect.selectValue 也就是選中值,以前說過該組件實現數據的雙向綁定,當Select組件 v-model 綁定的值變更時,須要同步到下拉項。

["fatSelect.selectValue"]: {
    handler(newValue) {
        const {
          value,
          label,
          fatSelect: { optionKey, multiple, selectValue }
        } = this;
        const key = this[optionKey] || this.$attrs[optionKey];

        if (
          newValue === value ||
          (Array.isArray(newValue) && newValue.find(item => item === value))
        ) {
          if (!multiple) {
            this.fatSelect.selectItems = [
              {
                key,
                label,
                value
              }
            ];
        } else {
            this.fatSelect.selectItems.push({
              key,
              label,
              value
            });
          }
        }
    },
    immediate: true
}
複製代碼

若是對應的 fatSelect.selectValue 變更時,要判斷當前選項的 optionKey 是否在 selectValue 中,若是存在,就將

this.fatSelect.selectItems = [
    {
        key,
        label,
        value
    }
];
複製代碼

3. 結論

忽略了 Select 組件的一些細節,例如多選、單選的邏輯,重點展現了下該組件的設計邏輯,以及數據綁定的實現方式,總結了一些實際業務中碰到的問題。

原創聲明: 該文章爲原創文章,轉載請註明出處。

相關文章
相關標籤/搜索