※この記事では呼び出し元コンポーネントを親、呼び出し先コンポーネントを子というように表現します。
個人的な体験談になってしまいますが、Vue.jsを初めて最初につまづいたのは、コンポーネント間のデータのやり取りでした。
親から子にデータは渡せても子からどうやって親のデータを更新するの?といろいろ疑問がありました。
今回はそんなコンポーネントの親子関係についてInputコンポーネントを作って解説していきたいと思います。
データの受け渡し
コンポーネントはpropsにより、データを受け取ることができます。言い換えると、親は子のpropsにデータを受け渡すことができ、子はpropsを通じて親からのデータを受け取ることができます。
- 子コンポーネント(child-component)
<template> <p>{{value}}</p> </template> <script> export default { props: { value: { type: String, default: '' } } } </script>
- 親コンポーネント(parent-component)
<template> <child-component :value="text" /> </template> <script> export default { data() { return { text: 'Hello World!!' } } } </script>
例えば、親コンポーネントのtextの値が変われば子コンポーネントのvalueの値も変わります。しかし、子コンポーネントのvalueの値が変わったとしても親コンポーネントのtextの値は変わりません。
通常のpropsによるデータの受け渡しでは親から子の変更は反映されても、子から親への変更は反映されません。
コンポーネント間のデータの同期
では子から親への変更は反映できないのでしょうか。答えは……
できます!
今回は次の簡単な2つのInputコンポーネント(MyInput, MyInput2)を基に説明していきます。
v-model
普通のinput要素について、入力データバインディングを行う場合は次のようにv-modelを使用します。
<input type="text" v-model="value">
これにより、入力された内容がvalueに同期されます。
独自で作成するコンポーネントについても上記のようにv-modelを定義することができます。 v-modelを定義するためには次の手順が必要になります。
valueプロパティ
v-modelを定義するにはpropsにvalueという名のプロパティを定義する必要があります。型などは自由ですが、大事なのはvalueという名前です。
export default { props: { value: { type: String, required: true } } }
v-modelで入力データバインディングを行う場合は、valueプロパティにその値が反映されます。
しかし、このままでは子から親への変更はまだ反映されません。
inputメソッド
子から親への変更を反映させるために以下のような算出プロパティを定義します。
export default { computed: { inputValue: { get() { return this.value; }, set(value) { this.$emit('input', value); } } } }
そしてこの算出プロパティをinputタグに入力データバインディングします。
<input :type="type" v-model="inputValue">
inputタグの内容にはinputValueのget、つまりpropsのvalueが表示されます。inputタグの内容を変更するとinputValueのset、つまりthis.$emit('input', value)
が呼び出されます。
ここが今回の肝となる部分です。
コンポーネントにはネイティブメソッドとしてinputメソッドが用意されています。inputメソッドは、v-modelで渡された親のデータを変更する役割があります。
例えば次のような場合、this.$emit('input', value)
は、親のinput1.idの値をvalueに変更するという動作をします。
<my-input v-model="input1.id"/>
MyInputの動作を簡単にまとめると次のようになります。
- v-modelによりpropsのvalueに親からデータを渡される
- inputタグにvalue(inputValue)を入力データバインディングする
- inputタグに文字を入力する(inputValueのsetが動作する)
this.$emit('input', value)
により親のデータが変更される- 親データの変更によりpropsのvalueの値が変更される
v-modelの問題点
v-modelによるデータの同期では、valueにデータを渡すことから、当然複数のデータを同期させることはできません。
このようなケースは個人的にあまりないような気がしますが、複数のデータを同期させるためには次に説明するsync修飾子を使用します。
sync修飾子
基本的な流れはv-modelの場合と同じですが、次の点が異なります。
- propsのプロパティ名に制限がない
- 親データの変更にはupdateメソッドを利用する
ではsync修飾子による実装方法を見ていきましょう。
プロパティの設定
上述したようにv-modelと違い、propsのプロパティ名に制限がないため、自由にプロパティを設定できます。
重要なのは親がそのプロパティをデータバインディングする方法で、次のようにsync修飾子を指定します。
<my-input :value.sync="input2.id">
これにより子コンポーネントのvalueプロパティにinput2.idの値を同期しますよ、という宣言になります。しかし、このままでは子から親への変更は反映されません。
updateメソッド
sync修飾子を利用した場合、親への変更を反映させるためにはネイティブメソッドであるupdateメソッドを利用します。
updateメソッドはthis.$emit('update:プロパティ名', 変更値)
の形で使用します。
MyInput2では次のように算出プロパティを定義しています。
export default { computed: { inputValue: { get() { return this.value; }, set(value) { this.$emit('update:value', value); } } } }
動作の流れとしてはv-modelの場合と同じのため割愛します。
複数データの同期の例
今回複数データの同期は実装していませんが、例えば次のように実装します。
export default { props: { value1: { type: String, required: true }, value2: { type: Boolean, required: true } }, computed: { inputValue1: { get() { return this.value1; }, set(value) { this.$emit('update:value1', value); } }, inputValue2: { get() { return this.value2; }, set(value) { this.$emit('update:value2', value); } } } }
あとはコンポーネントの呼び出し時にsync修飾子を付けるだけです。
<child-component :value1.sync="text" :value2.sync="isOk" />
まとめ
コンポーネントの親子関係は、最初は慣れないかもしれませんが、今回のような簡単なコンポーネントで練習していくと徐々に慣れてきます。
シンプルなものでいいのでいろんなコンポーネントを作ってみましょう。
- Vue.jsのおすすめ書籍はコチラ -
コメント