Vue 3 の emit を理解する
子コンポーネントから親へイベントを通知する仕組み。
defineEmits の基本から v-model 連携まで実例で整理します。
データの流れ
親コンポーネント ←──── emit(イベント通知)──── 子コンポーネント
────── props(データ渡し)───→
Vue のデータフローは一方向が基本。
親 → 子 は props、子 → 親 は emit です。
defineEmits の基本
<script setup> では defineEmits でイベントを宣言してから使います。
<!-- Child.vue -->
<script setup>
// 発火するイベント名を配列で宣言する
const emit = defineEmits(["close", "submit"]);
function handleClick() {
// 第1引数: イベント名 第2引数以降: 親に渡すデータ
emit("close");
emit("submit", { id: 1, name: "Alice" });
}
</script>
<template>
<button @click="handleClick">送信</button>
</template>
<!-- Parent.vue -->
<template>
<!-- @イベント名="ハンドラ" で受け取る -->
<Child @close="onClose" @submit="onSubmit" />
</template>
<script setup>
function onClose() {
console.log("閉じた");
}
function onSubmit(payload) {
console.log(payload); // { id: 1, name: 'Alice' }
}
</script>
v-model と emit — modelValue の役割
v-model は糖衣構文です。
親が v-model="value" と書くと、内部では次のように展開されます。
<!-- v-model="name" の正体 -->
<MyInput :modelValue="name" @update:modelValue="name = $event" />
つまり子が守るべき約束は2つ:
- props で
modelValueを受け取る - 変更時に
update:modelValueを emit する
<!-- MyInput.vue -->
<script setup>
defineProps({
modelValue: String, // ← 必ず "modelValue" という名前
});
const emit = defineEmits(["update:modelValue"]);
function onInput(e) {
emit("update:modelValue", e.target.value);
}
</script>
<template>
<input :value="modelValue" @input="onInput" />
</template>
<!-- Parent.vue -->
<script setup>
import { ref } from "vue";
const name = ref("");
</script>
<template>
<!-- これだけで双方向バインディングが完成 -->
<MyInput v-model="name" />
<p>入力値: {{ name }}</p>
</template>
複数の v-model — 引数付きで名前を変える
v-model:名前 という書き方で、1つのコンポーネントに複数の v-model を持てます。
props・emit の名前は 名前 と update:名前 になります。
<!-- UserForm.vue -->
<script setup>
defineProps({
firstName: String, // v-model:firstName に対応
lastName: String, // v-model:lastName に対応
});
const emit = defineEmits(["update:firstName", "update:lastName"]);
</script>
<template>
<input
:value="firstName"
@input="emit('update:firstName', $event.target.value)"
/>
<input
:value="lastName"
@input="emit('update:lastName', $event.target.value)"
/>
</template>
<!-- Parent.vue -->
<UserForm v-model:firstName="user.first" v-model:lastName="user.last" />
TypeScript で型を付ける
defineEmits に型引数を渡すと、emit するデータの型チェックが効きます。
<script setup lang="ts">
const emit = defineEmits<{
close: []; // 引数なし
submit: [payload: { id: number; name: string }];
change: [value: string];
}>();
emit("submit", { id: 1, name: "Alice" }); // ✓
// emit('submit', { id: '1' }) // 型エラー
</script>
まとめ
| 用途 | props 名 | emit するイベント名 | 親での記述 |
|---|---|---|---|
| 通常の v-model | modelValue | update:modelValue | v-model="x" |
| 名前付き v-model | firstName | update:firstName | v-model:firstName="x" |
| カスタムイベント | — | close / submit / 任意 | @close="fn" |
v-modelはmodelValue/update:modelValueで固定- それ以外のイベントは好きな名前で自由に設定できる
- TypeScript を使うなら型引数で安全に宣言する