Skip to content

20260706mon

Published: at 15:00

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つ:

<!-- 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-modelmodelValueupdate:modelValuev-model="x"
名前付き v-modelfirstNameupdate:firstNamev-model:firstName="x"
カスタムイベントclose / submit / 任意@close="fn"