每次寫筆記都在想要怎麼寫得讓大家(包含未來的我)看得懂,所以每次都要寫很久,但好像自己的內容有點太無聊了 XD 。
因自定義事件的名稱跟模組名稱和 props
命名不同,在使用時並不會有轉換的問題,所以怎麼命名就怎麼使用,命名用 myCustomEvent
,使用時也必須使用 v-on:myCustomEvent
,這時用 v-on:my-custom-event
是無效的。
在 HTML 中使用時,為了要遵守 HTML 的規範,所以自定義事件( Custom Events )名會被轉換成小寫,也因此在 HTML 中使用 v-on:myCustomEvent
時,會自動轉換成 v-on:mycustomevent
,最後會因為事件名稱不相同(myCustomEvent != mycustomevent
)而導致該監聽器無法作用。
因此 Vue 文件建議在一律以 kebab-case (短橫線分隔)來命名自定義事件( Custom Events )。
v-model
在講自定義事件的 v-model
前,先來了解一下 v-model
是如何產生作用的。
如果對 v-model
不太熟悉,可以先參考DAY09 | 跟 Vue.js 認識的30天 - Vue 的資料雙向綁定(v-model
)。
在一般情況下,我們想要雙向綁定就會使用 v-model
,如下:
<input type="text" v-model="name">
<input>
的 value 值會受 Vue 實例資料 name
的影響,而 name
也同樣受到 <input>
的 value 改變的影響。
但 v-model
是如何達到雙向綁定的,其實就是透過 v-bind:value
跟 v-on:input
<input type="text" v-bind:value="name" v-on:input="name = $event.target.value">
也就是說 v-model
做了2件事:
<input>
的 value 值綁定了 Vue 實例資料 name
。
當 <input>
在輸入( input
事件)時,會讓 Vue 實例資料 name
= 輸入的值(也就是 value )。
v-model
在模組上的 v-model
,做的事情跟上面的 <input>
很相似,但還是有些微不一樣,模組上的 v-model
是如何作用的。
在預設的情況下, v-model
被綁定的屬性是 value
, 被監聽的事件是 input
事件。
<!--以下2個 blog-input 做了相同的事情-->
<!--模組的 v-model 一樣是由 v-bind:value 跟 v-on:input 組成的-->
<!-- 使用 $event 接收 $emit 的第2個參數值-->
<blog-input v-model="title"><blog-post>
<blog-input v-bind:value="title" v-on:input="title = $event"><blog-post>
<script>
Vue.component('blog-input', {
props:['value'],
template:`<input type="text" :value="value" @input="$emit('input', $event.target.value)">`
})
</script>
v-model
在模組中使用 v-model
的時候,預設所綁定的屬性(prop)是 value
,預設綁定的事件是 input
事件。但是單選(type="radio"
)或複選(type="checkbox"
)按鈕中的 value
屬性並不是決定按鈕是否被點選的關鍵,決定點選的是 checked
屬性,所以可以透過模組 model
屬性來變更會影響按鈕結果的屬性名(prop)跟事件。
<!-- agree為布林值-->
<blog-checkbox v-model="agree"><blog-checkbox>
<blog-checkbox v-bind:checked="agree" v-on:change="agree = $event"><blog-checkbox>
<script>
Vue.component('blog-checkbox', {
model:{
// 預設的屬性跟事件
/*
prop:'value',
event: 'input'
*/
// 模組要綁定的屬性(prop)
prop:'checked',
// 模組要監聽的事件
event: 'change'
},
props:{
// 一定要聲明要綁定的屬性名
checked:Boolean
},
template:`<input type="checkbox" :checked="checked" @change="$emit('change', $event.target.checked)">`
})
</script>
.native
可以透過加入修飾符 .native
來讓模組標籤直接監聽原生事件(如 focus
、 click
等等非自定義的事件),而不需再透過內層 $emit
來傳遞事件。
<component-native @focus.native="focusCount"></component-native>
<script>
Vue.component('component-native',{
template:`<input type="text">`
})
</script>
加入修飾符 .native
後,模組標籤所監聽的事件就會被內層根元素所繼承,讓內層根元素監聽並傳遞同樣的事件,原理如下:
<component-native @focus="focusCount"></component-native>
<script>
Vue.component('component-native',{
template:`<input type="text" @focus="$emit('focus')">`
})
</script>
.native
無法作用的情況通常 input
標籤會伴隨著 label
出現,但這時如果一樣使用修飾符 .native
,繼承事件的根元素就是 label
而不是 input
。
同樣使用修飾符 .native
跟 focus
事件,測試看看:
<component-native-fail @focus.native="focusCount"></component-native-fail>
<script>
Vue.component("component-native-fail", {
// 繼承 focus 事件的是 label
template: `<label>姓名
<input type="text">
</label>`
});
</script>
同時間測試了一下其他的原生事件,將 focus
事件改成了 click
、 mouseout
事件後,發現 focusCount
是可以正常觸發的,所以後來想了下應該是因為只有 input
標籤有 focus
事件,而 label
標籤沒有,所以才會導致 focusCount()
觸發失敗。
.native
無法作用解決方法如果真的出現修飾符 .native
無法作用的狀況,用來取代修飾符 .native
的方法,就是透過 Vue 屬性 $listeners
來改變模組內的繼承元素。
<component-listener @focus="focusCount"></component-listener>
<script>
Vue.component("component-listener", {
// 繼承 focus 事件的是 label
template: `<label>姓名
<input type="text" v-on="$listeners">
</label>`
});
</script>
以上就是透過 v-on="$listeners"
讓模組標籤的事件繼承元素變成 <input>
而非 <label>
。
另外可以透過 computed
決定繼承的事件的狀況,例如某些事件要保留參數(預設是不帶第2個參數的),或是繼承的事件只有部分等等。
<component-listener-computed @focus="focusCount" @mouseout="mouseOutCount" v-model="text"></component-listener-computed>
<script>
Vue.component("component-listener-computed", {
props: ["value"],
template: `<label>姓名
<input type="text" v-on="customListeners">
</label>`,
computed:{
customListeners(){
// 排除 focus 事件
const {focus, ...listeners} = this.$listeners;
// 讓 input 事件能將參數傳遞出去
const vm = this;
listeners.input = (e)=>{
vm.$emit('input',e.target.value)
}
return listeners
}
}
});
</script>
有關 $listener
更多的資訊可以參考下方介紹:
QUICK VUE TIP - EVENT LISTENERS WITH THIS.$LISTENERS
.sync
在之前DAY12 | 跟 Vue.js 認識的30天 - Vue 模組資料傳遞(props
)有提到過 props 是單向數據流,內層無法改變外層的 props 值,如果真的要由內層去改動外層資料的話,就必須先用 props
傳遞資料進內層,再透過 $emit
將資料往外層傳遞。
<!-- 1 透過 props 先傳資料 name 進入 component -->
<!-- 3 新資料被傳遞出來後,在做更新,並影響 props 的值 -->
<!-- 4 將新 name 的值透過 props 在傳遞進去 component -->
<data-transfer :name="name" @update:name="name = $event"></data-transfer>
<script>
Vue.component('data-transfer',{
props:['name'],
// 2 透過$emit(自定義事件名,欲傳遞的值) 來將想改變的prop值傳送出去
template:`<div>
<span>姓名</span>
<p @click="$emit('update:name', 'Celeste')">{{name}}</p>
</div>`
})
const vm = new Vue({
el:'#vm',
data:{
name: 'Michelle'
}
})
</script>
要從內層去改變外層傳遞進來的 prop ,有一個更簡單的方法,那就是透過修飾符 .sync
來達成模組內外的 prop 值連動,修飾符 .sync
的原理就像上面的範例,但是使用 .sync
可以不用在外層模組標籤上寫下接收自定義事件。
要點就是外層加入要傳遞的 prop 上加入修飾符 .sync
,內層要改變該 prop 值的地方,使用 $emit('update:propName',該 prop 欲改變的值)
來做事件的傳遞。
<component-sync :name.sync="name"></component-sync>
<script>
Vue.component('component-sync',{
props:['name'],
template:`<div>
<span>姓名</span>
<span @click='$emit("update:name", "Linda")'>{{name}}</span>
</div>`
})
const vm = new Vue({
el:'#vm',
data:{
name: 'Michelle'
}
})
</script>
還可以結合 v-bind="物件名"
來一次雙向綁定所有物件屬性值到模組內。
v-bind="物件名"
一次綁定所有物件屬性的方法,可以參考Vue.js - 传入一个对象的所有 property。
<component-object-sync v-bind.sync="post"></component-object-sync>
<!-- v-bind="post" 的原理
<component-object-sync
v-bind:title="post.title"
v-bind:author="post.author"
v-bind:gender="post.gender"></component-object-sync>
-->
<script>
Vue.component('component-object-sync',{
// 外層真正綁定的 prop 是該物件的所有屬性名,而非整個 post,如果 props 寫成 post ,會出錯。
props:['title','author','gender'],
template:`<div>
<p><span>標題</span>
<span>{{title}}</span></p>
<p><span>請點作者</span>
<span @click='$emit("update:author", "Celeste")'>{{author}}</span></p>
<p><span>請點性別</span>
<span @click='$emit("update:gender", "female")'>{{gender}}</span></p>
</div>`
})
const vm = new Vue({
el: "#vm",
data: {
post:{
title:'Celeste\'s Blog',
author:'Michelle',
gender:'male'
}
}
});
</script>
修飾符 .sync
注意事項
不能結合使用表達式。
要綁定物件時,一定要使用變數,而不能直接使用物件實字(Object Literals)來綁定物件。
Demo:DAY13 | 跟 Vue.js 認識的30天 - Vue 模組自定義事件(Custom Events)
參考資料: