本篇文章教你們寫一個很是簡單的Select組件,想必不少人都寫過Select,畢竟它太經常使用了,可是本篇文章的示例使用到了Vue的自定義指令,若是你對Vue自定義指令不怎麼熟悉的話,本篇文章或許會讓您有所收穫!html
完成的效果圖以下: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: '地三鮮'
}
],
}
}
複製代碼
效果是這樣:git
下面可供選擇的options用的是絕對定位;同時input設置了readonly,使input變的不可輸入,總體佈局很簡單。github
接下來,咱們要添加兩個功能:express
這兩項目功能都挺簡單,先來完成第一個,點擊input框切換顯示options,藉助v-show就好。bash
<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
的布爾值。app
效果以下:ide
第二個,點擊下面的選項,將被選擇的展現到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值便可,同時,隱藏整個選項內容。佈局
效果以下:
從上面的效果圖中能夠看到,已經能夠正常選擇了,可是有一個問題,就是它選項內容展現的時候,咱們但願點擊其它空白的地方也可讓選擇內容隱藏,可是上面的代碼並無解決這個問題,接下來咱們來用兩種辦法來解決它。
其實,實現這個功能並不難,只是要想解決它就須要操做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自定義指令,在這個例子中須要明白如下基本知識點:
template
裏的某個元素上bind
,它在指令第一次綁定到元素上調用並且只調用一次,這個鉤子很重要,咱們在這個例子裏會用到;第二個是inserted
,它在元素插入到父元素的時候調用,官方文檔裏給了一個v-focus
的例子就用到了它;第三個和第四個分別是update
和componentUpdated
,前者是在vNode
更新時調用,後者是在更新完成後調用;最後是unbind
,在指令和元素解綁時調用。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)
}
}
}
複製代碼
代碼如上,效果以下:
看了有朋友在下面的評論,在多個Select時發現仍是有一點問題:
當有多個Select的時候,只有點擊了空白的其它地方的時候,展開的選項才都會消失,當其中一個選項展開再去選擇另外一個的時候,以前展開的選項不會被收起。產生這個問題的緣由是點擊input
部分的時候被阻止冒泡了。只要把它去掉同時把指定指令綁定的元素擴大到整個select而不是僅是以前的options部分就好了,由於在指令裏有了攔截。
<template>
<div class="select">
<div class="inner" v-clickOut="test"> //指令放到了這裏
<div class="inputWrapper" @click="showOptions = !showOptions"> //這裏阻止冒泡被去掉了
<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="choose(item.value)">{{item.value}}</li> // 這裏的點擊阻止冒泡也被去掉了
</ul>
</div>
</div>
</template>
複製代碼
效果:
最後把github地址放上:修改過的代碼地址
簡單總結一下:這是一個很是簡單的小例子,由於須要操做DOM,因此咱們選擇使用自定義來完成,固然咱們也可使用其它方法。只是,在咱們用Vue的時候,若是遇到須要操做DOM的時候,那麼能夠想一想可不能夠經過自定義指令來實現呢。這是我在掘金上的第七篇文章,感謝您的閱讀!