本文 AI 產出,尚未審核

Vue3 元件基礎 – 自訂事件名稱(kebab‑case vs camelCase)


簡介

在 Vue3 中,父子元件之間的溝通大多透過 props(向下傳遞)與 自訂事件(向上回報)完成。雖然 Vue 允許你自行命名事件,但若不熟悉 kebab‑casecamelCase 的差異,往往會造成事件監聽失效、程式碼可讀性下降,甚至埋下難以偵測的 bug。

本篇文章將深入說明在 Vue3 中 自訂事件名稱 的命名規則,為什麼官方建議使用 kebab‑case,什麼情況下可以使用 camelCase,以及如何在實務專案中正確、統一地使用這兩種寫法,讓你的元件 API 更易於維護與擴充。


核心概念

1. 為什麼會有兩種寫法?

  • kebab‑case:全部小寫,單詞之間以 - 連接(例如 my-event)。
  • camelCase:第一個單詞小寫,後續單詞首字母大寫(例如 myEvent)。

在 HTML 模板(template)裡,屬性與事件名稱會被 HTML 解析器 正規化為小寫,且 :@ 前的字串會自動轉成 kebab‑case。因此,在模板中使用 camelCase 會被自動轉換,而在 render functionJSX 中則保持原樣。

小結:若你在模板裡寫 @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-eventmyEvent 永遠不會被觸發。
最佳實踐:統一使用 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 中可以使用 camelCaseonMyEvent),但要記得在模板裡仍需 kebab‑case。
  • emits 物件可以加入型別驗證,提升開發時的即時錯誤提示。
  • 常見陷阱包括:在模板誤用 camelCase、混用大小寫、忘記在動態事件名稱上正規化、以及與原生 Web Component 互動時未遵守 kebab‑case。
  • 最佳實踐:全專案統一使用 kebab‑case 作為自訂事件的命名規則,除非確定只在 render/JSX 中使用,否則避免 camelCase。

透過正確的事件命名,你的 Vue3 元件將更具可讀性、可維護性,也能在大型專案或與其他前端技術(如 Web Component)共存時,避免不必要的錯誤與除錯成本。祝開發順利 🎉