コンポーネント
emit関数を理解するためには、コンポーネントの理解が必要不可欠です。
コンポーネントとは、UIを独立した再利用可能なピースごとに分割し、それぞれのピースを切り離して利用できるようにしたものです。アプリケーションはネストされたコンポーネントのツリーによって構成されているのが一般的です。
コンポーネントの例
<!-- Button.vue -->
<template>
<button class="btn">{{ label }}</button>
</template>
<script setup lang="ts">
defineProps<{ label: string }>()
</script>
<!-- App.vue -->
<template>
<Button label="送信" />
<Button label="キャンセル" />
</template>
<script setup lang="ts">
import Button from './Button.vue'
</script>
こうすることで:
- 同じUIを何度も再利用できる
- 役割ごとにコードを分離でき、保守性が高まる
- 親から子へデータを渡したり、子から親へイベントを返したりできる
親子間の関係
Vueでは、コンポーネント間のデータの流れは基本的に一方向(トップダウン)です。
つまり、データは「親 → 子」へと流れ、子が親の状態を直接変更することはできません。
基本的には親コンポーネントから子コンポーネントがデータを受け取る(props)事が可能です。しかし、親コンポーネントから受け取ったデータを変更した際、親コンポーネントにデータを返すことができません。
親コンポーネント
├─ propsでデータを渡す 〇
↓
子コンポーネント
├─ propsでデータを返す ✕
↑
親コンポーネント
そのために登場するのが今回説明するemit関数です。この関数により親コンポーネントでイベントを発火することで、データの変更等を行えるようにします。
親コンポーネント
├─ propsでデータを渡す 〇
↓
子コンポーネント
├─ emitでイベントを返す 〇
↑
親がそのイベントを受け取って処理
emit関数
イベントの発行と購読
子コンポーネントから親コンポーネントに何かを通知したいとき、Vue では emit による「カスタムイベント」を使います。
// 子コンポーネント
<!-- MyComponent.vue -->
<template>
<button @click="$emit('someEvent')">Click Me</button>
</template>
// 親コンポーネント
<MyComponent @some-event="callback" />
子側で emit('someEvent') を呼び出し、親側で @some-event="..." とハンドラを受けることで通信できます。
ポイント:
- イベント名は、子側で一度発行(emit)して、親側で受け取り(@event-name)ます。
- 親→子の props とは異なり、「子→親」の通知を明示的に設計する必要があります。
- コンポーネントから発行されたイベントはバブリング(子から孫→親へ伝播)しません。直接の子から発行されたイベントのみを購読できます。
emitイベント名と、テンプレート側のリスナーの表記が違う理由
emitイベント(someEvent)と、リスナー (@some-event) の表記が違うように見えるのは、Vueが内部で「ケバブケース(kebab-case)」に正規化して扱っているためです。
基本ケース:
- JavaScript 内部($emitなど)ではキャメルケース
- テンプレート(HTML)内ではケバブケース
コンポーネントやpropsでも同様です。
引数付きイベント(ペイロード)
emitには、イベント名だけでなく追加の引数(ペイロード)を渡すことができます。
<button @click="$emit('increaseBy', 1)">
Increase by 1
</button>
<MyButton @increase-by="(n) => count += n" />
この仕組みを使えば、「子が発生させるイベントに任意のデータ(ID・値・オブジェクト等)を付加して、親に伝える」ことが可能です。
発行するイベントの宣言
イベントを使う側・発行する側で、どのイベントを発行するかを明示的に宣言することが推奨されています。
この宣言によって:
- 発行可能なイベントを文書化できる
- Vue が未知のイベントに対する警告を出せる可能性がある(typo など防止)
- 型付きで記述すれば TypeScript でも補完・検証が効く
defineEmits() マクロを使用して、コンポーネントが発行するイベントを明示的に宣言できます。
<script setup>
defineEmits(['inFocus', 'submit'])
</script>
templateで使用した$emitメソッドは、コンポーネントのscript setupセクション内ではアクセスできませんが、代わりにdefineEmits()が同等の関数を返してくれるので、それを使用します。
<script setup>
const emit = defineEmits(['inFocus', 'submit'])
function buttonClick() {
emit('submit')
}
</script>
defineEmits()マクロは関数の中では使用できません。上記の例のように、script setup内に直接記述する必要があります。
script setupの代わりに明示的なsetup関数を使う場合は、イベントはemitsオプションを使って宣言する必要があり、emit関数はsetup()コンテキスト上で公開されます。
export default {
emits: ['inFocus', 'submit'],
setup(props, ctx) {
ctx.emit('submit')
}
}
イベントのバリデーション
オブジェクト構文で emits/defineEmits を書くことで、ペイロード(引数)の検証ロジックを定義することも可能です。このようなバリデーション関数を返すことで、発行時に引数が妥当かどうかをチェックできます。
const emit = defineEmits({
submit(payload: { email: string, password: string }) {
if (payload.email && payload.password) {
return true
} else {
console.warn('Invalid submit event payload!')
return false
}
}
})
参考
