Select 元件主要特點在於:當選項過多時,使用下拉選單展示並選擇內容。
- 資料雙向繫結,下拉選單變動時,選中項如何回顯;
- 單選、多選的區分,以及對應處理。
1. 例項
程式碼
<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 例項
程式碼地址:Github UI-Library
2. 原理
Select 元件的基本結構如下
主要可分為兩個部分:
- 顯示框:用來展示已經選中項,包含取消按鈕;
- 下拉框:包含已選中的高亮項,禁用項,預設選擇選項等,具備點選選中,再次點選取消的操作。
fat-select 顯示框:
<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
事件,如果失焦就收起下拉框。
<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
進行區分。
同時配置 v-model
相關屬性,同時監測 watch
相關 value
具體如下
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
向其所有下拉框注入一個依賴,用於訪問 selectValue
和 selectItems
等prop和data。
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"]
將上述 provide
的 Select 元件注入到當前選項中,
通過 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 元件的一些細節,例如多選、單選的邏輯,重點展示了下該元件的設計邏輯,以及資料繫結的實現方式,總結了一些實際業務中碰到的問題。
往期文章:
- 從零實現Vue的元件庫(零)- 基本結構以及構建工具
- 從零實現Vue的元件庫(一)- Toast 實現
- 從零實現Vue的元件庫(二)- Slider 實現
- 從零實現Vue的元件庫(三)- Tabs 實現
- 從零實現Vue的元件庫(四)- File-Reader 實現
- 從零實現Vue的元件庫(五)- Breadcrumb 實現
- 從零實現Vue的元件庫(六)- Hover-Tip 實現
- 從零實現Vue的元件庫(七)- Message-Box 實現
- 從零實現Vue的元件庫(八)- Input 實現
- 從零實現Vue的元件庫(九)- InputNumber 實現
- 從零實現Vue的元件庫(十)- Select 實現
- 從零實現Vue的元件庫(十一)- Date-picker 實現
- 從零實現Vue的元件庫(十二)- Table 實現
- 從零實現Vue的元件庫(十三)- Pagination 實現
- 從零實現Vue的元件庫(十四)- RadioGroup 實現
- 從零實現Vue的元件庫(十五)- CheckboxGroup 實現
原創宣告: 該文章為原創文章,轉載請註明出處。