使用v-model實現(xiàn)select下拉框組件

一、組件使用場景及需求分析

  • 表單多個固定單值的情況,我們不用再去input框輸入值,直接在固定的值里面去選擇

  • 選擇以后父組件綁定的值對應改變,使得不需要發(fā)送表單前再進行賦值

  • 選擇前后,列表都是不可見的

二、開始我們的codeing

  • 首先我們需要的是一個有所有單值選項的list展示

  • 然后是一個展示當前選擇的文字框

像這樣的:

01.png

這一步我們只需要父組件傳遞單值代碼,然后當前選中的一個值,沒有的話就默認為空。

vue:

<div class="fd-select-box">
   <p v-text="scoped.selected&&scoped.selected.name?scoped.selected.name:'請選擇'"></p>
   <span :class="fd-arrow icon iconfont">&#xe6a4;</span>
   <ul class="fd-select-list">
     <li v-for="(item,index) in list"
     :key="index+'select'"
     :class="{'active':scoped.selected&&item.code===scoped.selected.code}">
     {{item.name}}</li>
   </ul>
 </div>

JS:

 props: {
   list: {
   type: Array,
   required: true,
   },
   selected: Object,
 },
 data() {
   return {
     scoped: {
     // 當前選中的
     selected: this.selected,
     },
   };
 },

CSS:

.fd-select-box {
   position: relative;
   width: 200px;
   padding-right: 40px;
   padding-left: 10px;
   height: 36px;
   margin: 30px auto;
   line-height: 36px;
   border: 1px solid #41b883;
   border-radius: 4px;
   color: #000;
   font-size: 14px;
   text-align: left;
   cursor: pointer;
   box-sizing: border-box;
?
   .fd-arrow {
     position: absolute;
     top: 0;
     right: 0;
     font-size: 30px;
     transition: all 200ms;
?
       &.fd-down {
         transform: rotate(180deg);
         }
     }
?
 .fd-select-list {
   position: absolute;
   width: 100%;
   max-height: 200px;
   overflow: auto;
   list-style: none;
   top: 36px;
   left: 0;
   background: #fff;
   box-shadow: 0 0 5px rgba(0,0,0,0.2);
   z-index: 9;
?
     li {
       padding-left: 12px;
       line-height: 30px;
       cursor: pointer;
?
     &:hover {
       background: rgba(65, 191, 138, 0.2);
     }
?
     &.active {
       background: rgba(65, 191, 138, 0.9);
       color: #fff;
     }
 }
 }
 }

這樣,我們已經(jīng)把外層框架搭建好了。

接下來,解決子組件改變,父組件對應的值發(fā)生改變。

一般情況我們會想到子組件把當前選中的值通過this.$emit傳給父組件,然后父組件再在對應方法里面給對應的值賦值。今天我們用另外一種方法來解決這個問題,那就是v-model,相信我,用了它你會愛上它。

好了,我們看看官方文檔怎么說的:

一個組件上的 v-model 默認會利用名為 value 的 prop 和名為 input 的事件,但是像單選框、復選框等類型的輸入控件可能會將 value attribute 用于不同的目的。model 選項可以用來避免這樣的沖突:

 model: {
   prop: 'checked',
   event: 'change'
 },
 props: {
   checked: Boolean
 },
 template: `
   <input
     type="checkbox"
     v-bind:checked="checked"
     v-on:change="$emit('change', $event.target.checked)"
   >
 `
})

https://cn.vuejs.org/v2/guide/components-custom-events.html

我的理解就是提供了v-model;在自定義組件上model里的prop里的字段的值會直接賦給props里面對應字段,像之前我們給checked傳值是在父組件上通過:checked='false'這樣一種形式?,F(xiàn)在我們可以使用v-model='false'。來看具體在select框里面的表現(xiàn)吧。

export default {
   name: 'fdSselect',
   model: {
   prop: 'selected',
   event: 'changeValue',
 },
 props: {
   list: {
     type: Array,
     required: true,
   },
   selected: Object,
 },
 data() {
   return {
     scoped: {
     // 是否展示下面的列表
     showFlag: false,
     // 當前選中的
     selected: this.selected,
     },
     };
 },
 methods: {
   // 值改變后傳給父組件,因為組件定義了model,所以父組件相當于執(zhí)行了綁定的model值=emit出去的值
   changeValue(item) {
     this.scoped.selected = item;
     this.scoped.showFlag = false;
     this.$emit('changeValue', this.scoped.selected);
     },
   },
};

父組件調(diào)用:

 <fd-select :list="selectList" v-model="selected"></fd-select>

上面的event是我們要emit出去的事件名。這一步相當于在父組件執(zhí)行了父組件的this.selected等于子組件的this.scoped.selected;所以其實你用組件的時候v-model="value"其實就是:value="value" @change="(val) => {value = val}";

現(xiàn)在看看我們實現(xiàn)的效果:

02.gif

前兩個需求已經(jīng)實現(xiàn)了,最后一個需求是在交互上的優(yōu)化。

首先他要一開始的時候不展示,我們給一個控制下拉框顯隱的變量。showFlag默認值為false;點擊輸入框時展開下拉列表。然后選中選項后隱藏下拉列表。

注意我們的頁面結(jié)構(gòu),下拉列表是輸入框的子元素,所以點擊下拉列表元素的時候會涉及到事件冒泡,這個時候我們使用.stop修飾符來組織時間冒泡導致下拉列表一直不能隱藏。

vue:

 <div class="fd-select-box" @click="changeShow">
   <p v-text="scoped.selected&&scoped.selected.name?scoped.selected.name:'請選擇'"></p>
   <span :class="['fd-arrow icon iconfont',{'fd-down':scoped.showFlag}]">&#xe6a4;</span>
   <ul class="fd-select-list" v-show="scoped.showFlag">
     <li v-for="(item,index) in list"
         :key="index+'select'"
         @click.stop="changeValue(item)"
          :class="{'active':scoped.selected&&item.code===scoped.selected.code}">
           {{item.name}}</li>
   </ul>
</div>

JS:

 // 值改變后傳給父組件,因為組件定義了model,所以父組件相當于執(zhí)行了綁定的model值=emit出去的值
 changeValue(item) {
   this.scoped.selected = item;
   this.scoped.showFlag = false;
   this.$emit('changeValue', this.scoped.selected);
 },
 // 改變下拉選項的顯隱
 changeShow() {
   this.scoped.showFlag = !this.scoped.showFlag;
 },

繼續(xù)優(yōu)化,我們現(xiàn)在實現(xiàn)了組件列表的顯隱,但是只有操作當前組件時可以控制。那么我們點擊其他地方的時候,其實也是希望組件列表可以隱藏起來的。

實現(xiàn)這個的思路:綁定一個點擊事件在頁面上,只要點擊的元素不是當前組件,那么我們就可以隱藏當前組件的列表。這里我用到了自定義指令,具體實現(xiàn)如下:

clickOutside: {
   bind(el, binding) {
   function clickHandler(e) {
   // 這里判斷點擊的元素是否是本身,是本身,則返回
   if (el.contains(e.target)) {
     return false;
   }
   // 判斷指令中是否綁定了函數(shù)
   if (binding.expression) {
   // 如果綁定了函數(shù) 則調(diào)用那個函數(shù),此處binding.value就是handleClose方法
     binding.value(e);
   }
   return true;
 }
 // 給當前元素綁定個私有變量,方便在unbind中可以解除事件監(jiān)聽
   el.vueClickOutside = clickHandler;
   document.addEventListener('click', clickHandler);
 },
 unbind(el) {
   // 解除事件監(jiān)聽
   document.removeEventListener('click', el.vueClickOutside);
   delete el.vueClickOutside;
   },
 },

最后實現(xiàn)效果如圖:

03.gif

后期待優(yōu)化:實現(xiàn)可搜索的下拉框-->實現(xiàn)可以遠程搜索的下拉框

以上為個人編寫,希望能對大家的項目有所幫助,如有不當以及有更好的方法歡迎交流。

項目地址: https://github.com/jasminezx/select.git

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容