前言:
今天再次看了看vue文檔,又找到了知識盲區。
對於 將原生事件綁定到組件 ,文檔有講,別人也有寫博客。
但我還是想根據自己的理解把這一內容講清楚
總的來說,有三種方法:
-
使用native修飾符
-
使用$emit分發事件
-
使用$listeners
正文:
首先,show code
html中代碼
<div id="app">
<my-button @click='handleClick'></my-button>
</div>
js中代碼
Vue.component('my-button',{ template:`<button>點擊</button>`
}) const vm = new Vue({ el:'#app', methods:{ handleClick(){ console.log('click') } } })
在這里,我們定義了my-button子組件,在父組件中引用了,然后我們想在上面綁定click事件,觸發handleClick回調。
那么,可以猜猜有沒有觸發?
——結果很容易猜到,沒有觸發,click沒有打印輸出。
為什么?
——因為vue有自己事件運行機制,my-button不是原生DOM元素,我們是無法直接給其綁定原生事件並觸發的
所以就有解決辦法一:使用native修飾符
我們僅需要在click后加上.native就可,如下
<div id="app">
<my-button @click.native='handleClick'></my-button>
</div>
相當於我們會把事件放在原生button標簽上,此時事件便觸發,click就打印了。
雖然這個方法使用起來非常簡單,但是其存在局限性:它只會把事件放在子組件的根標簽上。
上面子組件的根標簽就是button,自然就觸發了。
但是某些情況下,將某些事件綁定在根標簽而非目標標簽時,是無法觸發事件的。如下情況:
<div id="app2">
<my-input @focus.native='handleFocus'></my-input>
</div>
Vue.component('my-input',{ template:` <label for=""> label: <input type="text">
</label> ` }) const vm2 = new Vue({ el:'#app2', methods:{ handleFocus(){ console.log('focus...') } } })
盡管我們使用了native修飾符,但是focus事件放在子組件根標簽——label標簽上,無法觸發該事件。
所以就有解決辦法二:使用$emit分發事件
Vue.component('my-input',{ template:` <label for=""> label: <input type="text" @focus='$emit("focus","子組件的value")'>
</label>
` }) const vm2 = new Vue({ el:'#app2', methods:{ handleFocus(value){ console.log('focus...',value) //focus... 子組件的value
} } })
在子組件input標簽中綁定focus事件,其回調中使用$emit分發事件,使父組件事件觸發。
$emit()有兩個參數:
第一個參數為分發的事件名,在這里為focus,也可改別的,只需要與父組件中給子組件標簽上綁定的事件名一致即可
第二個參數為給父組件該事件傳的參數,我們在父組件中的該事件回調中就可接受到。所以我們一般想將子組件的數據傳給父組件,完成父子組件間的通信,就可使用$emit。
除了這個解決方法外,還有第三種:使用$listeners
$listeners 它是一個對象,里面包含了作用在這個組件上的所有監聽器。
Vue.component('my-input',{ template:` <label for=""> label: <input type="text" v-on='$listeners'>
</label>
` })
(其余代碼同上一個方法,故省略)
在input上使用 v-on="$listeners" ,就是將所有的事件監聽器指向這個input元素。
故也同樣能打印出,且value值為event對象。
相比起方法二,這個$listener的使用更加全面——
若是方法二,再在子組件標簽上綁定多個事件,那就要在子組件進行相應的寫事件名進行$emit分發
而這個方法,就已經將所有事件監聽綁在input元素上了,就不用再次設置
而且,你也可以再次設置,$listeners的使用是很靈活的
你可以自定義監聽器,或者覆蓋一些監聽器的行為。
在下面的代碼中,重寫了focus事件監聽器
Vue.component('my-input',{
template:` <label for=""> label: <input type="text" v-on='inputListeners'>
</label>
`, computed:{ inputListeners(){ return Object.assign({},this.$listeners,{ focus:(event)=>{ this.$emit('focus',event.target.value) } }) } } })
首先,我們得明白 v-on:xxx = fn 等價於 v-on={xxx:fn}
(tips:這里不可寫為@={xxx:fn}。所有指令的縮寫 @ : # 等等,都是在其有參數的情況下使用)
其次,inputListeners是一個計算屬性,返回的是一個對象,是將 $listeners 和 你重寫的事件的對象 合並的對象
還有,在這里重寫監聽器,還是用到了$emit,每個監聽器都得到event對象,我們可以取出event.target.value傳給父組件
最后,父組件就能在focus時觸發事件,並得到子組件傳來的值了
拓展:
當我們使用 $attrs 和 $listeners 時,my-input就相當於一個完全透明包裹器了。
怎么理解這句話呢?
——前面我們已經知道了 v-on = $listeners 會把 所有父組件綁定到該子組件上的事件都放在該元素上
而使用 v-bind = $attrs 會把 除在props中聲明了的,除style和class 的父傳子的參數 都放在該元素上。
那么 當我們同時使用兩者放到某個元素上時 ,就已經把父組件所有放在子組件標簽(my-input)上的屬性、事件, 全都放在了該元素上
此時,事件調用、屬性獲取都不再有障礙, my-input 不就可以理解成是透明的了嘛~
三種方法都能使原生事件綁定到組件上,就寫法上當然是第一種最簡單,第三種更麻煩。但是只要理解了就都挺好寫的了。
但是在使用時,還是根據需求來,若是就是想綁定到組件的根標簽上,直接使用第一種即可。否則,便使用二或三。
參考自vue文檔:將原生事件綁定到組件