從零實現Vue的元件庫(十)- 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元件的基本結構如下

主要可分為兩個部分:
- 顯示框:用來展示已經選中項,包含取消按鈕;
- 下拉框:包含已選中的高亮項,禁用項,預設選擇選項等,具備點選選中,再次點選取消的操作;
- 確保每個下拉項唯一,即使存在相同 label 的情況。
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 } ]; 複製程式碼