This article was translated from Japanese by AI and may contain inaccuracies. For the most accurate content, please refer to the original Japanese version.
white diamond

Vue.js でジェネリックコンポーネントを使う

Vue.js 3.3 から `<script setup>` 構文を使用してコンポーネントを記述する際に、型引数を指定することができるようになりました。TypeScript で関数に型引数を指定するのと同じように、コンポーネントの props の型を指定することができます。

Vue.js 3.3 から <script setup> 構文を使用してコンポーネントを記述する際に、型引数を指定できるようになりました。TypeScript で関数に型引数を指定するのと同じように、コンポーネントの props の型を指定できます。

すでに React においては、コンポーネントに型引数を指定してジェネリックコンポーネントとして扱うことができましたが、Vue.js でも同様のことができるようになりました。

基本的な構文

<script> タグに generic 属性を指定することで、型引数を指定できます。

components/MyComponent.vue
<script setup lang="ts" generic="T">
import { defineProps } from 'vue'
 
const props = defineProps<{
  value: T
}>()
</script>

通常の型引数と同様に、extends キーワードを使用して、型引数に制約をつけることもできます。以下の例において型引数 T を持つ value Props は string 型か number 型である必要があります。

components/MyComponent.vue
<script setup lang="ts" generic="T extends string | number">
const props = defineProps<{
  value: T;
}>();
</script>

, で区切って複数の型引数を指定することもできます。

components/MyComponent.vue
<script setup lang="ts" generic="T extends string | number, U">
const props = defineProps<{
  value: T;
  value2: U;
}>();
</script>

ジェネリックコンポーネントの使用例

<script setup> 構文を使用して、ジェネリックコンポーネントを使用する例を紹介します。ここでは <SelectBox> コンポーネントを作ってみましょう。セレクトボックスをコンポーネントと定義するとき、value Props が受け入れる型は選択肢が持つ型に制限したくなるでしょう。例えば選択肢として "red", "blue", "green" がある場合、型引数を使用しない場合次のようになるでしょう。

components/ColroSelectBox.vue
<script setup lang="ts">
type Color = "red" | "green" | "blue";
 
const { items, value } = defineProps<{
  items: Color[];
  value: Color;
}>();
 
const emit = defineEmits<{
  (e: "change", value: Color): void;
}>();
 
const handleChange = (event: Event) => {
  const target = event.target as HTMLSelectElement;
  emit("change", target.value as Color);
};
</script>
 
<template>
  <select :value="value" @change="handleChange">
    <option
      v-for="item in items"
      :key="item"
      :value="item"
      :selected="item === value"
      @change="() => emit('change', item)"
    >
      {{ item }}
    </option>
  </select>
</template>

defineProps でコンポーネントの Props を定義する際に、選択肢を表す items と値を表す value 型に同じ Color 型を指定しています。また同様に defineEmits でイベントを定義する際にも、value 型に Color 型を指定してセレクトボックスの選択肢に持つ型のみを受け入れるようにしています。

このコンポーネントを使用する際と、value Props にカーソルを合わせたときに "red", "green", "blue" のいずれかの文字列が候補として表示されるようになります。また、"red", "green", "blue" 以外の文字列を指定すると型エラーが発生するようになります。

コンポーネントの Props が受け入れる型を制限することで恩恵を受けることができますが、このコンポーネントは汎用的に使用できないという欠点があります。別の種類の選択肢をもつセレクトボックスを作成するたびに、Color 型だけを変更した似た処理を複数のコンポーネントで実装しなければいけません。

そこで型引数を使用したジェネリックコンポーネントを作成することで、様々な選択肢を受け入れる汎用的なコンポーネントを作成できます。<script> タグに generic 属性を指定して型引数を指定し、Color 型を型引数 T に置き換えてみましょう。

components/SelectBox.vue
<script setup lang="ts" generic="T extends string | number">
const { value, items } = defineProps<{
  items: T[];
  value: T;
}>();
 
const emit = defineEmits<{
  change: [value: T];
}>();
 
const handleChange = (event: Event) => {
  const target = event.target as HTMLSelectElement;
  emit("change", target.value as T);
};
</script>
 
<template>
  <select :value="value" @change="handleChange">
    <option
      v-for="item in items"
      :key="item"
      :value="item"
      :selected="item === value"
      @change="() => emit('change', item)"
    >
      {{ item }}
    </option>
  </select>
</template>

この <SelectBox> コンポーネントは先程の <ColorSelectBox> コンポーネント同じように、value Props が受け取る型を items の型に制限できます。

まとめ

  • <script> タグに generic 属性を指定することで、コンポーネントの型引数を指定できるようになった
  • 型引数を使用することで、汎用的なコンポーネントを作成できるようになる

参考