使用v-model实现select下拉框组件

一、组件使用场景及需求分析

  • 表单多个固定单值的情况,我们不用再去input框输入值,直接在固定的值里面去选择

  • 选择以后父组件绑定的值对应改变,使得不需要发送表单前再进行赋值

  • 选择前后,列表都是不可见的

二、开始我们的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;
     }
 }
 }
 }

这样,我们已经把外层框架搭建好了。

接下来,解决子组件改变,父组件对应的值发生改变。

一般情况我们会想到子组件把当前选中的值通过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'这样一种形式。现在我们可以使用v-model='false'。来看具体在select框里面的表现吧。

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,所以父组件相当于执行了绑定的model值=emit出去的值
   changeValue(item) {
     this.scoped.selected = item;
     this.scoped.showFlag = false;
     this.$emit('changeValue', this.scoped.selected);
     },
   },
};

父组件调用:

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

上面的event是我们要emit出去的事件名。这一步相当于在父组件执行了父组件的this.selected等于子组件的this.scoped.selected;所以其实你用组件的时候v-model="value"其实就是:value="value" @change="(val) => {value = val}"

现在看看我们实现的效果:

02.gif

前两个需求已经实现了,最后一个需求是在交互上的优化。

首先他要一开始的时候不展示,我们给一个控制下拉框显隐的变量。showFlag默认值为false;点击输入框时展开下拉列表。然后选中选项后隐藏下拉列表。

注意我们的页面结构,下拉列表是输入框的子元素,所以点击下拉列表元素的时候会涉及到事件冒泡,这个时候我们使用.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,所以父组件相当于执行了绑定的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;
 },

继续优化,我们现在实现了组件列表的显隐,但是只有操作当前组件时可以控制。那么我们点击其他地方的时候,其实也是希望组件列表可以隐藏起来的。

实现这个的思路:绑定一个点击事件在页面上,只要点击的元素不是当前组件,那么我们就可以隐藏当前组件的列表。这里我用到了自定义指令,具体实现如下:

clickOutside: {
   bind(el, binding) {
   function clickHandler(e) {
   // 这里判断点击的元素是否是本身,是本身,则返回
   if (el.contains(e.target)) {
     return false;
   }
   // 判断指令中是否绑定了函数
   if (binding.expression) {
   // 如果绑定了函数 则调用那个函数,此处binding.value就是handleClose方法
     binding.value(e);
   }
   return true;
 }
 // 给当前元素绑定个私有变量,方便在unbind中可以解除事件监听
   el.vueClickOutside = clickHandler;
   document.addEventListener('click', clickHandler);
 },
 unbind(el) {
   // 解除事件监听
   document.removeEventListener('click', el.vueClickOutside);
   delete el.vueClickOutside;
   },
 },

最后实现效果如图:

03.gif

后期待优化:实现可搜索的下拉框-->实现可以远程搜索的下拉框

以上为个人编写,希望能对大家的项目有所帮助,如有不当以及有更好的方法欢迎交流。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,189评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,577评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,857评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,703评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,705评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,620评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,995评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,656评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,898评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,639评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,720评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,395评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,982评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,953评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,195评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,907评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,472评论 2 342