DAY13 | 跟 Vue.js 認識的30天 - Vue 模組自定義事件(Custom Events)
每次寫筆記都在想要怎麼寫得讓大家(包含未來的我)看得懂,所以每次都要寫很久,但好像自己的內容有點太無聊了 XD 。
自定義事件( Custom Events )命名
因自定義事件的名稱跟模組名稱和 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
,如下:
1 | <input type="text" v-model="name"> |
<input>
的 value 值會受 Vue 實例資料 name
的影響,而 name
也同樣受到 <input>
的 value 改變的影響。
但 v-model
是如何達到雙向綁定的,其實就是透過 v-bind:value
跟 v-on:input
1 | <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
事件。
1 | <!--以下2個 blog-input 做了相同的事情--> |
模組中的單選或複選按鈕的 v-model
在模組中使用 v-model
的時候,預設所綁定的屬性(prop)是 value
,預設綁定的事件是 input
事件。但是單選(type="radio"
)或複選(type="checkbox"
)按鈕中的 value
屬性並不是決定按鈕是否被點選的關鍵,決定點選的是 checked
屬性,所以可以透過模組 model
屬性來變更會影響按鈕結果的屬性名(prop)跟事件。
1 | <!-- agree為布林值--> |
模組的修飾符
.native
可以透過加入修飾符 .native
來讓模組標籤直接監聽原生事件(如 focus
、 click
等等非自定義的事件),而不需再透過內層 $emit
來傳遞事件。
1 | <component-native @focus.native="focusCount"></component-native> |
加入修飾符 .native
後,模組標籤所監聽的事件就會被內層根元素所繼承,讓內層根元素監聽並傳遞同樣的事件,原理如下:
1 | <component-native @focus="focusCount"></component-native> |
修飾符 .native
無法作用的情況
通常 input
標籤會伴隨著 label
出現,但這時如果一樣使用修飾符 .native
,繼承事件的根元素就是 label
而不是 input
。
同樣使用修飾符 .native
跟 focus
事件,測試看看:
1 | <component-native-fail @focus.native="focusCount"></component-native-fail> |
同時間測試了一下其他的原生事件,將 focus
事件改成了 click
、 mouseout
事件後,發現 focusCount
是可以正常觸發的,所以後來想了下應該是因為只有 input
標籤有 focus
事件,而 label
標籤沒有,所以才會導致 focusCount()
觸發失敗。
修飾符 .native
無法作用解決方法
如果真的出現修飾符 .native
無法作用的狀況,用來取代修飾符 .native
的方法,就是透過 Vue 屬性 $listeners
來改變模組內的繼承元素。
1 | <component-listener @focus="focusCount"></component-listener> |
以上就是透過 v-on="$listeners"
讓模組標籤的事件繼承元素變成 <input>
而非 <label>
。
另外可以透過 computed
決定繼承的事件的狀況,例如某些事件要保留參數(預設是不帶第2個參數的),或是繼承的事件只有部分等等。
1 | <component-listener-computed @focus="focusCount" @mouseout="mouseOutCount" v-model="text"></component-listener-computed> |
有關 $listener
更多的資訊可以參考下方介紹:
QUICK VUE TIP - EVENT LISTENERS WITH THIS.$LISTENERS
.sync
在之前DAY12 | 跟 Vue.js 認識的30天 - Vue 模組資料傳遞(props
)有提到過 props 是單向數據流,內層無法改變外層的 props 值,如果真的要由內層去改動外層資料的話,就必須先用 props
傳遞資料進內層,再透過 $emit
將資料往外層傳遞。
1 | <!-- 1 透過 props 先傳資料 name 進入 component --> |
要從內層去改變外層傳遞進來的 prop ,有一個更簡單的方法,那就是透過修飾符 .sync
來達成模組內外的 prop 值連動,修飾符 .sync
的原理就像上面的範例,但是使用 .sync
可以不用在外層模組標籤上寫下接收自定義事件。
要點就是外層加入要傳遞的 prop 上加入修飾符 .sync
,內層要改變該 prop 值的地方,使用 $emit('update:propName',該 prop 欲改變的值)
來做事件的傳遞。
1 | <component-sync :name.sync="name"></component-sync> |
還可以結合 v-bind="物件名"
來一次雙向綁定所有物件屬性值到模組內。
v-bind="物件名"
一次綁定所有物件屬性的方法,可以參考Vue.js - 传入一个对象的所有 property。
1 | <component-object-sync v-bind.sync="post"></component-object-sync> |
修飾符 .sync
注意事項
不能結合使用表達式。
要綁定物件時,一定要使用變數,而不能直接使用物件實字(Object Literals)來綁定物件。
Demo:
See the Pen DAY13 | 跟 Vue.js 認識的30天 - Vue 模組自定義事件(Custom Events) by Celeste6666 (@celeste6666) on CodePen.
參考資料: