Edited at

[Vue] 親コンポーネントと子コンポーネントで双方向バインディングする

More than 1 year has passed since last update.


概要

vuejsにて、親コンポーネントから子コンポーネントに対して初期値を与え、子コンポーネントは自身のUIで値を書き換えて、親コンポーネント側のデータも動的に更新させる方法メモ。vuex使えよとかはとりあえず無しで。

以下のGIFの場合、親コンポーネントは子コンポーネントに対して、choiceとtextの初期値をバインドする。子コンポーネントはバインドされたデータを、自身のUIで変更し、変更を親コンポーネントに伝える。親コンポーネントは変更されたデータをリアルタイムに画面に表示する。

fff.gif

2018/04/21 追記

本記事で扱っている仕組みは、.sync修飾子を用いることでより簡略的に実現することができます。が、2.x系の.sync修飾子は1.x系のそれとは異なり、本記事で扱っている内容に近い糖衣構文となっています。個人的にそれが躓きやすいポイントになると思うので、本記事では.sync修飾子を使わずに実現しています。


前提

以下環境で動作確認

debian
8.6

vue
2.5.9

Chrome
63.0


親コンポーネント

<template>

<div class="parent">
<h1>親コンポーネント</h1>
{{ data }}
<child v-model="data"/>
</div>
</template>

<script>
export default {
data: function() {
return {
data: {
choice: ['A', 'B'],
text: '初期値',
}
}
},
components: {child: require('./child')}
}
</script>


子コンポーネント

<template>

<div class="child">
<h2>子コンポーネント</h2>
<input type="checkbox" value="A" v-model="choice">A
<input type="checkbox" value="B" v-model="choice">B
<input type="checkbox" value="C" v-model="choice">C
<input type="text" v-model="text">
</div>
</template>

<script>
export default {
data: function() {
return {
text: '',
choice: [],
}
},
updated: function() {
this.$emit('input', {
choice: this.choice,
text: this.text,
})
},
mounted: function() {
this.choice = this.value.choice
this.text = this.value.text
},
props: {
value: {
type: Object,
required: true,
},
},
}
</script>


解説


v-modelによるデータバインド

まず、カスタムコンポーネント(今回の場合child)に対して、v-modelを定義すると、内部的には以下のように変換がかかる

変換前

<child v-model="data" />

変換後

<child v-bind:value="data"

v-on:input="data = $event.target.value"
/>

省略記法で書くなら

<child :value="data"

@input="data = $event.target.value"
/>

つまり、子コンポーネントからは、value属性で親コンポーネントからデータを受け取ることができ、inputメソッドを発火することでデータを更新することができるようになる。


子コンポーネントで初期値の設定

子コンポーネント側では、textとchoiceをそれぞれ定義しておく。

data: function() {

return {
text: '',
choice: [],
}
},

コンポーネントが読み込まれたタイミングで、親コンポーネントから渡された値でデータを初期化する。

mounted: function() {

this.choice = this.value.choice
this.text = this.value.text
},

もし親コンポーネントからバインドされるv-modelが変化する場合は、mountedでなくwatchで、valueが変化するたびにデータを更新すればよい。

watch: {

value() {
this.choice = this.value.choice
this.text = this.value.text
}
},


子コンポーネントでのデータの更新を親コンポーネントに伝搬する

子コンポーネントのUIより、データが書き換わった場合に、親コンポーネントにそれを通知する。前述の通り、v-modelでバインドされたデータに対して、inputメソッドを発火することで親コンポーネントのデータを更新することができる。親コンポーネントのメソッドを呼び出す方法については[Vue] 親コンポーネントのデータ/メソッドを子コンポーネントから使うを参照。

updated: function() {

this.$emit('input', {
choice: this.choice,
text: this.text,
})
},

これによって、本来は単方向バインディングしか提供されていないVueで、双方向バインディングのようなものを実現できる。