採点されません。匿名です。 間違えることは学習の一部です。読むだけでもかまいません。

Vue + TS — Emits 型付け

テクノロジ系 / L06 vue-typescript — TypeScript 連携

Vue + TS — Emits 型付け

Function 形式

<script setup lang="ts">
const emit = defineEmits<{
  (e: "increment"): void;
  (e: "set", value: number): void;
  (e: "submit", payload: { name: string; age: number }): void;
}>();

function handleClick() {
  emit("increment");
  emit("set", 100);
  emit("submit", { name: "Taro", age: 20 });
}
</script>

(e: "name", ...args): void の形式で複数並べる。

Object 形式 (推奨・新しめ)

<script setup lang="ts">
const emit = defineEmits<{
  increment: [];
  set: [value: number];
  submit: [payload: { name: string; age: number }];
}>();
</script>

event名: [引数の型] で書ける。

Vue 3.3+ 推奨、読みやすい。

親側

<Counter
  @increment="handleIncrement"
  @set="(v) => count = v"
  @submit="(p) => save(p)"
/>

引数の型が自動推論される (TS の恩恵)。

defineModel (Vue 3.4+)

defineProps + defineEmits を 1行に圧縮:

<script setup lang="ts">
const text = defineModel<string>();
const isOpen = defineModel<boolean>("open", { default: false });
</script>

<template>
  <input v-model="text" />
  <Dialog v-model:open="isOpen" />
</template>

ラッパー input / dialog / select で激減。

共有型

// types/events.ts
export type FormSubmitEvent = {
  name: string;
  age: number;
  email: string;
};
<script setup lang="ts">
import type { FormSubmitEvent } from "@/types/events.js";

const emit = defineEmits<{
  submit: [event: FormSubmitEvent];
}>();
</script>

複数 component で同じ event を扱うときに共有。

まとめ

  • 型ベース defineEmits<{...}>() で型安全
  • Function 形式と Object 形式 (3.3+ 推奨)
  • 親側の handler 引数が自動推論
  • defineModel (3.4+) で v-model を簡潔に
  • 共有型は別ファイル + import type

L06 完。次は L07 テスト。

考えてみよう

既定では正解・不正解の判定はしません。自分のペースで「答えを見る」を。採点してほしい場合だけ「採点してほしい」を押してください。

Lv1: 基本 — Object 形式 (3.3+)

問題

<!-- Counter.vue -->
<script setup lang="ts">
const emit = defineEmits<{
  increment: [];
  set: [value: number];
  submit: [payload: { name: string; age: number }];
}>();
</script>

<template>
  <button @click="emit('increment')">+</button>
  <button @click="emit('set', 100)">100</button>
  <button @click="emit('submit', { name: 'Taro', age: 20 })">Submit</button>
</template>

<!-- 親 -->
<Counter
  @increment="handleInc"
  @set="(v) => handleSet(v)"
  @submit="(p) => handleSubmit(p)"
/>

合格基準

  • 3 emit が型付きで親に届く
  • 引数も型推論

Lv2: 応用 — Function 形式と比較

問題

同じものを 2形式で書き、好みを論じる。

// Function 形式 (旧)
const emit = defineEmits<{
  (e: "increment"): void;
  (e: "set", value: number): void;
}>();

// Object 形式 (3.3+ 推奨)
const emit = defineEmits<{
  increment: [];
  set: [value: number];
}>();

合格基準

  • 両方動く
  • Object 形式の利点 (短い・読みやすい) を理解

Lv3: 発展 — defineModel + 複数

問題

<!-- NameForm.vue -->
<script setup lang="ts">
const firstName = defineModel<string>("first", { default: "" });
const lastName = defineModel<string>("last", { default: "" });
</script>

<template>
  <input v-model="firstName" placeholder="First" />
  <input v-model="lastName" placeholder="Last" />
</template>

<!-- 親 -->
<NameForm v-model:first="fn" v-model:last="ln" />
<p>{{ fn }} {{ ln }}</p>

合格基準

  • 2 v-model が独立に動く
  • defineModel で defineProps + defineEmits を集約

提出

submission.md に3問の回答を。