本篇文章教大家寫一個非常簡單的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)
}
}
}
複製程式碼
程式碼如上,效果如下:
四、補充和修改
看了有朋友在下面的評論,在多個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的時候,那麼可以想想可不可以通過自定義指令來實現呢。這是我在掘金上的第七篇文章,感謝您的閱讀!