はじめに
本稿は Vue 2.x が対象になります。Vue 3 に関する記事はこちらを参照ください。
Vue.js のコンポーネント間のデータのやりとりについて、親コンポーネントから子コンポーネントへデータを渡すには、v-bind
属性と props
オプションを利用します。これにより親コンポーネント側でデータを変更すれば、子コンポーネントの props
経由で渡されたデータも変更されます。しかし、子コンポーネント側で props
経由で渡されたデータを直接変更することは非推奨です。しかし、子コンポーネントの変更を親コンポーネントへ反映したい場合があります。たとえば、再利用性のある入力フォームのコンポーネントを親子間でやりとりするケースです。この問題を解決するには v-model
を正しく理解する必要があります。本稿で紹介するコードは CodeSandbox にて共有します。
サンプルコード
御復習い
v-model
は双方向データバインディングを実現するディレクティブです。双方向データバインディングとは、ビューで変更があった場合、その値を Vue インスタンスのデータとして更新します。逆に Vue インスタンスのデータに変更があった場合はビューを再レンダリングします。
<input v-model="message" />
v-model
はユーザーの入力イベントにおいてデータを更新するための糖衣構文です。たとえば、以下はテキスト入力欄の糖衣構文の事例です。
<input
:value="message"
@input="message = $event.target.value"
/>
また、v-model
は <input>
要素の type
属性の値に応じて異なる属性のバインディング、イベントを伝播します。
- テキストと複数行テキストは
value
属性とinput
イベント - チェックボックスとラジオボタンは
checked
属性とchange
イベント - 選択フィールドは
value
属性とchange
イベント
さらにコンポーネントの v-model
の糖衣構文は以下のようになります1。
<CustomTextField
:value="message"
@input="message = $event"
/>
本題
子コンポーネントの変更を親コンポーネントへ反映する方法をテキスト入力欄の事例で紹介します。
テキスト入力欄
まずは親コンポーネントについて説明します。v-bind
属性経由で子コンポーネントにデータ(message
)を渡すための準備をします。props
は親コンポーネントの v-bind
属性経由で渡します。
<!-- コードは一部省略しています。 -->
<CustomTextField :value="message" />
<script>
export default {
data() {
return {
message: "",
};
},
};
</script>
v-bind
属性経由で渡されたデータは子コンポーネントの props
オプションで定義します。
<script>
// props オプションの定義
export default {
props: {
value: {
type: String,
},
},
};
</script>
上記のように props
オプションを定義すれば、コンポーネントをインスタンス化したときにオブジェクトのプロパティとして利用できます。つまり、data
と同様にテンプレート中で展開できます。それでは子コンポーネントの value
属性に props
で定義したvalue
を v-bind
属性でバインディングしましょう。
<input :value="value" />
子コンポーネントから親コンポーネントへの通信では、$emit
メソッドでイベントを送出します。それでは子コンポーネントの入力イベントを親コンポーネントへ通信するための準備をしましょう。
<input
:value="value"
@input="$emit('input', $event.target.value)"
/>
input
イベントをハンドリングして、$emit
メソッドで親コンポーネントにイベントを送出します。$emit
メソッドの第 2 引数には子コンポーネントで入力された値を指定します。これにより親コンポーネントの v-on
ディレクティブを使ってイベントを受け取る際、子コンポーネントで入力された値を取得できます。
<CustomTextField
:value="message"
@input="message = $event"
/>
親コンポーネント側でも input
イベントをハンドリングして、子コンポーネントで入力された値を親コンポーネントで管理している message
に代入します。これで子コンポーネントの変更を親コンポーネントへ反映できます。しかし、このままでは少し冗長なのでもう少しスッキリした書き方があります。それが v-model
です。v-model
をカスタムコンポーネントで利用する場合、上記の糖衣構文になります。
<CustomTextField v-model="message" />
ちなみに子コンポーネントでも同様に v-model
を利用したい場合は、算出プロパティの getter
関数と setter
関数を利用します。算出プロパティはデフォルトでは getter
関数のみですが、必要があれば setter
関数も使えます。
<!-- v-model を利用した場合 -->
<template>
<input v-model="inputedValue" />
</template>
<script>
export default {
props: {
value: {
type: String,
},
},
computed: {
inputedValue: {
get() {
return this.value;
},
set(newValue) {
this.$emit("input", newValue);
},
},
},
};
</script>
input
イベントでハンドリングして、v-bind
属性でバインディングした value
属性を setter 関数の引数に渡し、$emit
メソッドで親コンポーネントに入力されたデータを伝播します。以上で v-model の親子間コンポーネントのデータ伝播に関する説明を終えますが、これがテキスト入力欄ではなく他の type
属性の <input>
要素ではどうすればよいかをチェックボックスを事例に説明します。
チェックボックス
チェックボックスがチェックされているかどうかの checked
属性値は親コンポーネントで管理します。子コンポーネントは props
経由で checked
属性値を受け取ります。チェックボックスの ON / OFF の change
イベント時に checked
属性値を親に伝搬します。
<template>
<label>
<input type="checkbox" @change="$emit('change', $event.target.checked)" />
{{ checked }}
</label>
</template>
<script>
export default {
props: {
checked: {
type: Boolean,
},
},
};
</script>
親コンポーネントは checked
属性を v-bind
属性経由で子コンポーネントに伝搬します。また、子コンポーネントの change
イベントをハンドリングします。イベントハンドラの引数を data
オプションで管理している checked
に代入します。
<CustomCheckbox
:checked="checked"
@change="checked = $event"
/>
前例のテキスト入力欄では上記のコードを v-model
で置換しましたが、コンポーネントの v-model
の糖衣構文とは異なります。では v-model
に置換できないかというと答えは置換できます。子コンポーネントに model
オプションを指定します。
<script>
export default {
model: {
prop: "checked",
event: "change",
},
props: {
checked: {
type: Boolean,
},
},
};
</script>
model
オプションの props
と event
をチェックボックスの続生とイベントに合わせることで、親コンポーネントで v-model
に置換できます。
<CustomCheckbox v-model="checked" />
ちなみに、子コンポーネントで v-model
を利用したい場合は、前例のテキスト入力欄と同様に実装になります。
<template>
<label>
<input type="checkbox" v-model="inputedValue" />
{{ checked }}
</label>
</template>
<script>
export default {
model: {
prop: "checked",
event: "change",
},
props: {
checked: {
type: Boolean,
},
},
computed: {
inputedValue: {
get() {
return this.checked;
},
set(newValue) {
this.$emit("change", newValue);
},
},
},
};
</script>
さいごに
いかがでしょうか。どちらも v-model
を正しく理解していなければ一見なにをやっているのか把握しにくいかと思います。v-model
に限らず正しく仕様を理解することは重要です。本稿がどなたかの一助になれば幸甚です。