视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
使用Vue自定义指令实现Select组件
2020-11-27 22:14:06 责编:小采
文档


本篇文章教大家写一个非常简单的Select组件,想必很多人都写过Select,毕竟它太常用了,但是本篇文章的示例使用到了Vue的自定义指令,如果你对Vue自定义指令不怎么熟悉的话,本篇文章或许会让您有所收获!

完成的效果图如下:

 

一、首先,我们简单布局一下:

<template>
 <div class="select">
 <div class="inner">
 <div class="inputWrapper">
 <input type="text" readonly placeholder="请选择菜品">
 <span class="iconfont icon-zhankaishangxia"></span>
 </div>
 <ul class="options">
 <li v-for="(item, index) in options" :key="index">{{item.value}}</li>
 </ul>
 </div>
 </div>
</template> 
......
data() {
 return {
 options: [
 {
 value: '西红柿鸡蛋'
 },
 {
 value: '青椒抱鸡蛋'
 },
 {
 value: '回锅肉'
 },
 {
 value: '宫保鸡丁'
 },
 {
 value: '地三鲜'
 }
 ],
 }
}

效果是这样:

 

下面可供选择的options用的是绝对定位;同时input设置了readonly,使input变的不可输入,整体布局很简单。

二、开始添加功能

接下来,我们要添加两个功能:

  • 点击上面的input框,可以切换显示下面的options
  • 选择options里的某个选项后让它展示在input里,同时让选项部分消失
  • 这两项目功能都挺简单,先来完成第一个,点击input框切换显示options,借助v-show就好。

    <div class="inputWrapper" @click="showOptions = !showOptions">
     <input type="text" readonly placeholder="请选择菜品">
     <span class="iconfont icon-zhankaishangxia"></span>
    </div>
    <ul class="options" v-show="showOptions" v-show="showOptions"> //添加v-show
     <li v-for="(item, index) in options" :key="index">{{item.value}}</li>
    </ul>
    ......
    data() {
     showOptions: false
    }

    如上所示,在选项里添加 v-show="showOptions" 并将 showOptions 初始化为 false 。同时,在包裹 input 的 div 上添加 click 事件来回切换 showOptions 的布尔值。

    效果如下:

     

    第二个,点击下面的选项,将被选择的展示到input里,同时让options消失,也不难。

    <div class="inputWrapper" @click="showOptions = !showOptions">
     <input type="text" readonly placeholder="请选择菜品" :value="selected"> //这里用value绑定一个data值selected
     <span class="iconfont icon-zhankaishangxia"></span>
    </div>
    <ul class="options" v-show="showOptions">
     <li v-for="(item, index) in options" :key="index" @click="choose(item.value)">{{item.value}}</li>
    </ul>
    ......
    data() {
     return {
     ......
     showOptions: false
     selected: ''
     }
    },
    methods: {
     choose(value) {
     this.showOptions = false
     if (value !== this.selected) {
     this.selected = value
     }
     }
    }

    逻辑很简单,在input里用value绑定一个data值,点击选择某个选项后,将选项的内容赋给这个data值即可,同时,隐藏整个选项内容。

    效果如下:

     

    从上面的效果图中可以看到,已经可以正常选择了,但是有一个问题,就是它选项内容展示的时候,我们希望点击其它空白的地方也可以让选择内容隐藏,但是上面的代码并没有解决这个问题,接下来我们来用两种办法来解决它。

    3、常规的DOM操作 VS Vue自定义指令

    其实,实现这个功能并不难,只是要想解决它就需要操作DOM

    <div class="inputWrapper" @click.stop="showOptions = !showOptions"> //注意这里的stop修饰器
     <input type="text" readonly placeholder="请选择菜品" :value="selected">
     <span class="iconfont icon-zhankaishangxia"></span>
    </div>
    <ul class="options" v-show="showOptions">
     <li v-for="(item, index) in options" :key="index" @click.stop="choose(item.value)">{{item.value}}</li> //还有这里的stop修饰器
    </ul>
    ...
    data() {
     return {
     ......
     showOptions: false
     }
    }
    mounted() {
     let that = this
     document.addEventListener('click', function() {
     that.showOptions = false
     })
    }

    上面的代码有两点:一个是在mounted后面给整个document添加了点击事件,这样在点击时候就可以将options隐藏,但是,我们在点击输入框部分和选项内容时,我们不希望它触发,而是让它走我们之前写好的逻辑,所以给两个 click 事件都添加了 stop 修饰器来阻止冒泡,这样,点击到它们的时候就不会冒泡到 document 上面了。效果如下:

     

    到这里基本功能都写完了,可以通过添加 $emit 和 props 来进行数据传递,让它更加通用些。但是最后关于点击其它地方让选项部分消失的功能,我们还可以再完善下,可以考虑使用Vue指令的方式实现。

    关于Vue指令,官方文档里有比较清楚的说明,如果不是特别明白可以点击这里先看看!

    关于Vue自定义指令,在这个例子中需要明白以下基本知识点:

    它是用来操作DOM的,所以所有Vue指令都会挂在 template 里的某个元素上

    它有4个钩子函数,一是 bind ,它在指令第一次绑定到元素上调用而且只调用一次,这个钩子很重要,我们在这个例子里会用到;第二个是 inserted ,它在元素插入到父元素的时候调用,官方文档里给了一个 v-focus 的例子就用到了它;第三个和第四个分别是 update 和 componentUpdated ,前者是在 vNode 更新时调用,后者是在更新完成后调用;最后是 unbind ,在指令和元素解绑时调用。

    这4个钩子函数可以 都至少可以传3个参数 ,第一是 el 就是被绑定指令的元素,第二个 binding , 它是个对象 ,而且 它的一些属性特别有用 ,它的属性包括 name , expression 和 value 等,当然不只这三个,但是我们这个例子要用。举个例子: 假如我写一个自定义指令 v-example="test" ,而这个 test 是我在 methods 里写的一个方法,那么就可以通过 binding.name 拿到 example 字符串,可以通过 binding.value 拿到 test 函数本身并且执行。如果这里不明白没关系接下来我们会说到。

    如果仔细观察,它们非常像 Vue 本身的生命周期钩子函数,只是它们是作用在指令上与元素的上的。从 bind 最开始绑定到最后 unbind 解绑完成了一个完整的周期。

    好了,我们把之前 mounted 写的DOM操作相关的东西都删掉,开始动手写一个自定义指令。

    <ul class="options" v-show="showOptions" v-clickOut="test"> //这里使用了下面的自定义指令,并将一个test方法传递进去了
     <li v-for="(item, index) in options" :key="index" @click.stop="choose(item.value)">{{item.value}}</li>
    </ul>
    ...
    methods: {
     ......
     test() { //test函数,它作为参数传递给了指令
     console.log('这是一个测试函数')
     }
    }, 
    directives: { //这里是自定义指令
     clickOut: { // 这里是自定义的v-clickOut指令
     bind: function(el, binding) { // bind钩子函数,当它与元素绑定的时候就会执行
     console.log('el===>', el)
     console.log('binding.name===>', binding.name)
     console.log('binding.expression===>', binding.expression)
     console.log('binding.value===>', binding.value)
     }
     }
    }

    上面的代码都有清楚的注释说明,我们自定义了一个 clickOut 的指令,并且把它挂到了一个元素上,而且给它传了一个 test 方法,我们来看看 console.log 出的东西都是些啥。

     

    从上面的图片可以看出当指令和元素绑定的时候即 bind 的时候,它会执行bind函数获得很多有用的东西,上面我们讲了 bind 函数里有几个重要的参数,从打印出的结果里我们非常清楚地看到,el就是指令绑定的元素本身,binding是一个对象,它获得了很多有用的东西,包括传递进来的函数。

    明白了它的基本构造,我们就来继续完善这个指令。

    <ul class="options" v-show="showOptions" v-clickOut="test">
     <li v-for="(item, index) in options" :key="index" @click.stop="choose(item.value)">{{item.value}}</li>
    </ul>
    ...
    methods: {
     test() {
     this.showOptions = false 
     }
    },
    directives: {
     clickOut: {
     bind: function(el, binding) {
     document.addEventListener('click', function(e) {
     if (el.contains(e.target)) return false
     if (binding.expression) {
     binding.value()
     }
     })
     }
     }

    看下上面改写过的代码做了些啥? 说下逻辑:当我们自定的 v-clickOut 与选项部分的ul元素绑定的时候,我们监听document的click事件,如果点击的元素是被指令绑定的元素的子元素或是被绑定元素本身,那就什么都不做;如果不是,那就执行传递进来的test函数。而test函数执行的结果就是把选项部分隐藏。

    逻辑很清楚。

    当然我们可以继续完善它。我们给 document.addEventListener 了,也可以在 合适的时候 removeEventListener ,这个合适的时候就是 unbind 钩子函数。

    所以我们可以完善下:

    ......
    directives : {
     clickOut: {
     bind: function(el, binding) {
     function handler(e) {
     if (el.contains(el.target)) return false
     if (binding.expression) {
     binding.value()
     }
     }
     el.handler = handler
     document.addEventListener('click', el.handler)
     },
     unbind: function(el) {
     document.removeEventListener('click', el.handler)
     } 
     }
    }

    代码如上,效果如下:

     

    简单总结一下:这是一个非常简单的小例子,因为需要操作DOM,所以我们选择使用自定义来完成,当然我们也可以使用其它方法。只是,在我们用Vue的时候,如果遇到需要操作DOM的时候,那么可以想想可不可以通过自定义指令来实现呢!

    下载本文
    显示全文
    专题