Vue3 元件基礎 – 自訂事件名稱(kebab‑case vs camelCase)
簡介
在 Vue3 中,父子元件之間的溝通大多透過 props(向下傳遞)與 自訂事件(向上回報)完成。雖然 Vue 允許你自行命名事件,但若不熟悉 kebab‑case 與 camelCase 的差異,往往會造成事件監聽失效、程式碼可讀性下降,甚至埋下難以偵測的 bug。
本篇文章將深入說明在 Vue3 中 自訂事件名稱 的命名規則,為什麼官方建議使用 kebab‑case,什麼情況下可以使用 camelCase,以及如何在實務專案中正確、統一地使用這兩種寫法,讓你的元件 API 更易於維護與擴充。
核心概念
1. 為什麼會有兩種寫法?
- kebab‑case:全部小寫,單詞之間以
-連接(例如my-event)。 - camelCase:第一個單詞小寫,後續單詞首字母大寫(例如
myEvent)。
在 HTML 模板(template)裡,屬性與事件名稱會被 HTML 解析器 正規化為小寫,且 :、@ 前的字串會自動轉成 kebab‑case。因此,在模板中使用 camelCase 會被自動轉換,而在 render function 或 JSX 中則保持原樣。
小結:若你在模板裡寫
@myEvent="handler",Vue 其實會把它視為@my-event。這是導致事件監聽失效的常見原因。
2. 在子元件 emit 時的命名
子元件使用 emit 時,名稱不會被自動轉換,所以你可以自行決定使用 kebab‑case 或 camelCase。為了保持一致性,官方文件建議 在子元件中使用 kebab‑case,這樣在父層模板監聽時不會產生誤差。
// ChildComponent.vue
export default {
emits: ['update:model-value', 'my-event'], // 建議使用 kebab‑case
methods: {
// 觸發自訂事件
trigger() {
this.$emit('my-event', { payload: 123 })
}
}
}
3. 父層模板如何監聽
3.1 使用 kebab‑case(最安全)
<!-- ParentComponent.vue -->
<template>
<ChildComponent @my-event="handleMyEvent" />
</template>
<script>
import ChildComponent from './ChildComponent.vue'
export default {
components: { ChildComponent },
methods: {
handleMyEvent(payload) {
console.log('收到事件 payload:', payload)
}
}
}
</script>
3.2 使用 camelCase(僅在 render / JSX 中有效)
// ParentComponent.jsx
import { defineComponent } from 'vue'
import ChildComponent from './ChildComponent.vue'
export default defineComponent({
components: { ChildComponent },
setup() {
const handleMyEvent = (payload) => {
console.log('JSX 收到 payload:', payload)
}
return () => (
<ChildComponent onMyEvent={handleMyEvent} />
)
}
})
注意:在普通
.vue模板裡仍須寫成@my-event,否則會被 HTML 正規化成my-event,導致找不到對應的事件。
4. emits 選項的型別檢查(Vue 3.3+)
Vue 3.3 引入了 事件參數的型別驗證,可以在 emits 陣列改寫為物件,配合 TypeScript 或 JSDoc 取得更好的開發體驗。
export default {
// 以 kebab‑case 為鍵,提供驗證函式
emits: {
'my-event'(payload) {
return typeof payload === 'object' && 'payload' in payload
}
},
methods: {
trigger() {
this.$emit('my-event', { payload: 456 })
}
}
}
5. 常見範例彙總
| 範例 | 子元件 emit | 父層監聽方式 | 重點說明 |
|---|---|---|---|
| 基本範例 | this.$emit('my-event') |
<Child @my-event="handler" /> |
使用 kebab‑case,最不易出錯 |
| 帶參數 | this.$emit('update:model-value', newVal) |
<Child @update:model-value="onUpdate" /> |
v-model 內建事件必須 kebab‑case |
| 在 JSX | emit('myEvent') |
<Child onMyEvent={handler} /> |
只能在 render / JSX 中使用 camelCase |
| 多字元 | emit('user-login-success') |
<Child @user-login-success="onLogin" /> |
多段式事件名稱,保持 kebab‑case 可讀性 |
| TypeScript | emit('my-event', payload as MyPayload) |
onMyEvent(payload: MyPayload) {} |
配合 emits 物件驗證,提高安全性 |
常見陷阱與最佳實踐
1. 模板中意外使用 camelCase
<!-- 錯誤寫法 -->
<ChildComponent @myEvent="handler" />
陷阱:HTML 會把
myEvent轉成myevent,Vue 再轉成my-event,最終找不到my-event事件。
解法:永遠在模板裡寫 kebab‑case,或改用 render/JSX。
2. v-model 的自訂事件名稱
Vue 3 允許自訂 v-model 的 prop 與事件名稱(modelValue / update:modelValue 為預設)。如果自行改成 model-value,仍須使用 kebab‑case:
<!-- 正確 -->
<Child v-model:custom="value" />
<!-- 子元件 -->
export default {
props: ['custom'],
emits: ['update:custom']
}
3. 在 emits 中混用大小寫
// ❌ 不建議
emits: ['myEvent', 'my-event']
原因:會讓維護者混淆,且在模板只能監聽
my-event,myEvent永遠不會被觸發。
最佳實踐:統一使用 kebab‑case 作為事件鍵名。
4. 使用動態事件名稱時的正規化
<!-- 動態監聽 -->
<Child :[eventName]="handler" />
若
eventName為'myEvent',Vue 仍會把它正規化為my-event。因此在程式碼裡最好直接提供 kebab‑case:
const eventName = 'my-event'
5. 跨框架或原生 Web Component 的互動
當 Vue 元件需要與 Custom Elements(原生 Web Component)溝通時,事件名稱必須遵守 Custom Event 的規範,即 kebab‑case(瀏覽器只接受小寫且以 - 分隔的名稱)。因此在這種情境下,使用 kebab‑case 是唯一正確的選擇。
實際應用場景
1. 表單元件的即時驗證
<!-- ParentForm.vue -->
<template>
<TextInput
v-model="email"
@validation-failed="showError"
/>
</template>
<script>
import TextInput from './TextInput.vue'
export default {
components: { TextInput },
data() {
return { email: '' }
},
methods: {
showError(msg) {
alert(`驗證失敗:${msg}`)
}
}
}
</script>
TextInput.vue 內:
export default {
emits: ['validation-failed'],
methods: {
validate() {
if (!/.+@.+/.test(this.modelValue)) {
this.$emit('validation-failed', '請輸入有效的 Email')
}
}
}
}
使用 kebab‑case 的 validation-failed,父層模板直接監聽,避免大小寫錯誤。
2. 圖表元件的互動回傳
在圖表套件(如 ECharts)裡,使用自訂事件回傳使用者點擊的資料點:
// ChartWrapper.vue
export default {
emits: ['point-clicked'],
mounted() {
this.chart.on('click', (params) => {
this.$emit('point-clicked', params.data)
})
}
}
父層:
<ChartWrapper @point-clicked="handleClick" />
此情境下,point-clicked 直接映射到圖表的原生事件名稱,保持一致性。
3. 與原生 Web Component 共存
<!-- 使用自訂元素 -->
<my-button @button-clicked="onClick" />
my-button(一個原生 Custom Element)只能發出 kebab‑case 事件 button-clicked,Vue 必須以相同名稱監聽,否則事件會被忽略。
總結
- 模板會自動把屬性與事件名稱正規化為 kebab‑case,因此在
.vue模板中 永遠使用 kebab‑case(@my-event)最安全。 - **子元件
emit**的名稱不會被自動轉換,建議 統一採用 kebab‑case,讓父層模板不必額外思考大小寫問題。 - render function / JSX 中可以使用 camelCase(
onMyEvent),但要記得在模板裡仍需 kebab‑case。 emits物件可以加入型別驗證,提升開發時的即時錯誤提示。- 常見陷阱包括:在模板誤用 camelCase、混用大小寫、忘記在動態事件名稱上正規化、以及與原生 Web Component 互動時未遵守 kebab‑case。
- 最佳實踐:全專案統一使用 kebab‑case 作為自訂事件的命名規則,除非確定只在 render/JSX 中使用,否則避免 camelCase。
透過正確的事件命名,你的 Vue3 元件將更具可讀性、可維護性,也能在大型專案或與其他前端技術(如 Web Component)共存時,避免不必要的錯誤與除錯成本。祝開發順利 🎉