PropsとEmitsに関する実験的な新機能
Vue3.3で導入されたPropsとEmitsに関する新機能のうち、ここからは実験的なもの、すなわち、先の新機能リストの3と4の2個を紹介します。
v-modelを利用した子から親への通信
そのひとつめは、defineModel()マクロです。子コンポーネントから親コンポーネントにデータを渡す場合、Emitによるイベントアップを利用します。その際、渡されたデータを親コンポーネントのリアクティブ変数に格納するだけならば、v-modelを利用した方法がこれまでも可能でした。
これは、例えば、親コンポーネントではリスト6、子コンポーネントではリスト7のようなコードを記述していました。
<script setup lang="ts"> : const memberListInit = new Map<number, Member>(); memberListInit.set(33456, {id: 33456, name: "田中太郎", email: "tanaka@taro.com", points: 35, note: "…"}); : const memberList = ref(memberListInit); : </script> <template> : <OneMember v-for="[id, member] in memberList" : v-model:points="member.points"/> // (1) : </template>
<script setup lang="ts"> : const onInput = (event: Event): void => { const element = event.target as HTMLInputElement; const inputPoints = Number(element.value); emit("update:points", inputPoints); // (1) } </script> <template> : <input type="number" v-bind:value="points" v-on:input="onInput"> // (2) : </template>
親コンポーネントでは、Propsとして渡すデータをv-bind:〇〇と記述する代わりに、v-model:〇〇と記述します。リスト6では、(1)のpointsが該当します。
一方、子コンポーネントでは、このPropsの値は、リスト7の(2)のように表示にはそのまま利用します。そして、その値を変更する場合、(1)のようにEmitを利用して新しい値を親コンポーネントに渡します。
その際、必ず、イベント名は「update:Prop名」とし、第2引数に新しい値を渡します。(1)では、イベント名を「update:points」とし、第2引数に(2)の数値入力コントロールに入力された数値であるinputPointsを渡しています。
この方法を利用することで、親コンポーネント側では、Emitに応じたメソッドを用意する必要がなく、新しい値(inputPoints)がmemberListのpointsに自動的に格納されます。
defineModel()
このv-modelによる新しい値の自動格納の仕組みを、より簡潔に記述できるようにしたものがdefineModel()マクロです。前項のリスト6とリスト7を、defineModel()を利用したものへと変更すると、それぞれ、リスト8とリスト9になります。
<OneMember v-for="[id, member] in memberList" : v-model="member.points"/> // (1)
<script setup lang="ts"> : const points = defineModel<number>(); // (1) </script> <template> : <input type="number" v-model="points"> // (2) : </template>
まず、親コンポーネントでは、リスト8の(1)のように単にv-modelとします。
一方、大きく変わるのが子コンポーネントであり、Emitの実行が不要となります。代わりに、リスト9の(1)のように、defineModel()マクロを実行した戻り値を利用します。その際、ジェネリクスとして、データ型を指定します。リスト9の(1)ではnumberとしています。
この戻り値(リスト9の(1)ではpoints)は、リアクティブな変数となっているので、その変数に新たな値を格納するだけです。リスト9の(2)では、ここでもv-modelを利用して数値入力コントロールに入力された数値と自動連動するようにしています。
もちろん、メソッドなどのスクリプトブロック内の処理において、次のコードのようにdefineModel()の戻り値変数のvalueプロパティに値を格納することも可能です。
points.value = newValue;
ただし、この仕組みを利用する際は、親子間でデータ連携させる変数(今回の例ならば、points)は、Propsの型定義に含めてはダメです。それは、リスト8の(1)とリスト9の(1)の記述の違い、すなわち、v-model:pointsが単なるv-modelになったことからも分かるように、通常のPropsとは別枠で親から子へデータが渡されるからです。この点には注意しておいてください。
vite.config.tsへの追記が必要
このdefineModel()を利用する際、もうひとつ注意が必要です。それは、現段階では実験的機能ですので、そのままでは利用できず、vite.config.tsへの追記が必要です。これは、リスト10の太字部分です。
export default defineConfig({ plugins: [ vue({ script: { defineModel: true } }), ], : })
Propsのデフォルト値
PropsとEmitsに関する実験的な新機能ふたつめは、Propsのデフォルト値に関するものです。Propsには、その値が存在しない場合のデフォルト値を設定する仕組みがあります。例えば、リスト11のようなProps定義では、備考欄にあたるnoteは?がついている通り、オプション扱いです。
interface Props { id: number; name: string; email: string; points: number; note?: string; }
もしこのnoteのデータが存在しない場合、画面に「--」のように表示させたいとするならば、この「--」をnoteのデフォルト値として設定できます。その場合は、defineProps()の実行時に、withDefaults()でラップするように、リスト12のようなコードを記述する必要があります。
const props = withDefaults( defineProps<Props>(), {note: "--"} );
このコードはいささか煩瑣であり、JavaScriptの分割代入とデフォルト値の構文を利用して、リスト13のように記述できればスッキリします。
const {note = "--"} = defineProps<Props>();
これまでもこの書式は、一見問題なく動作していましたが、このnoteがリアクティブな変数でなくなる問題点がありました。これが、Vue3.3からは改善され、問題なく利用できるようになりました。
ただし、defineModel()同様に、この仕組みも実験的なものですので、リスト14の太字の追記が、vite.config.tsに必要な点には注意してください。
export default defineConfig({ plugins: [ vue({ script: { propsDestructure: true } }), ], : })
まとめ
Vue3.3の新機能を紹介する本稿の前編は、いかがでしたでしょうか。
後編は、新機能リストの5であるジェネリクスコンポーネントを中心に、残りの6〜8も紹介します。