基礎からメモ: Vue.js CH5-p175 コンポーネントの双方向データバインディング
親子間でデータを受け渡ししたい場合、親から子へはprops
を介して渡す、子から親へはイベントを$emit
するという決まりになっていた。
$emit
を介さずに子からprops
で渡された値を更新しようとするとエラーになった。これは親のデータを子から簡単に変更できてしまっては問題があるからだ。
しかし子コンポーネントのインプットフォームに入力された値で、親コンポーネントのデータを更新したいといったケースは多い。v-model
を使えば少し簡易にこれが可能になる。
HTMLは単純に親コンポーネントのテンプレートを使っているとする。
<div id="app"> <my-component></my-component> </div>
その親のテンプレートでは、インプットフォームを持つ子のコンポーネントを使っているとする。
let myComponent = { template: `<div> <h4>My Calendar</h4> <my-calendar v-model="date"></my-calendar> <pre>{{ $data }}</pre> </div>`, data: function() { return { date: "2020" } } }; let app = new Vue({ el: '#app', components: { 'my-component': myComponent } });
親のdata
が変化するか分かるように、親のテンプレートの最後に{{ $data }}
を表示している。
子コンポーネントのカスタムタグには、v-model="date"
として親コンポーネントのデータを紐付ける。
子コンポーネント側では、インプットフォームのタグにv-on
でインプットイベント時の呼び出しメソッドと、インプットフォーム内の表示文字列を親から受け取ったdate
データにしている。受け取るデータの変数名はvalue
で、デフォルトの名前だが、props
で明示的に受取り、データ型も文字列に指定する。
Vue.component('my-calendar', { template: `<input class="my-calendar" v-on:input="updateModel" v-bind:value="value">`, props: { value: String }, methods: { updateModel: function(event) { this.$emit('input', event.target.value) } } });
これでインプットフォームに、親コンポーネントのdate
データ「2020」が初期値として表示され、何かを入力すると子コンポーネントのupdateModel
メソッドが呼ばれ、$emit
を介して親のdate
データも更新される。updateModel
メソッドにはevent
オブジェクトが渡されるので、$emit
の第二引数をevent.target.value
とすれば、親のdate
データに入力した値が放り込まれる。
この仕組みにはもうすこし簡単な書き方がある。子コンポーネントのメソッドを介さず、v-on:input
に直接$emit
を書いてしまう方法だ。イベントオブジェクトは$event
と書く。
Vue.component('my-calendar', { template: `<input class="my-calendar" v-on:input="$emit('input', $event.target.value)" v-bind:value="value">`, props: { value: String } });
v-modelで使うプロパティ名やイベント名をカスタマイズ
コンポーネント中のv-model
のデフォルトでは、プロパティ名をvalue
、イベント名をinput
として使う。ただしこの名前をmodel
オプションで任意の名前に変更できる。子コンポーネントのprops
で受け取る変数名をcurrent
、イベント名をchange
に変えてみる。
Vue.component('my-calendar', { template: `<input class="my-calendar" v-on:change="$emit('change', $event.target.value)" v-bind:value="current">`, model: { prop: 'current', event: 'change' }, props: { current: String } });
.sync
による双方向データバインディング
v-model
では単一の属性しか同期できない。複数の属性を同期させるには.sync
修飾子を使う。ただし子側からはあくまで$emit
でのイベント発火を介して親のデータの同期をする。
親コンポーネントで子コンポーネントのカスタムタグを使う際に、v-bind
と.sync
修飾子で複数のデータを紐付けておく。
let myComponent = { template: `<div> <my-component v-bind:name.sync="name" v-bind:hp.sync="hp"></my-component> <pre>{{ $data }}</pre> </div>`, data: function() { return { name: "スライム", hp: 100 } } }; let app = new Vue({ el: '#app', components: { 'my-component': myComponent } });
子側では、算出プロパティのセッターとゲッターを使って、v-model
で関連付けたデータの操作をする。
Vue.component('my-component', { template: `<div class="my-component"> <p>名前: {{ name }}, HP: {{ hp }}</p> <p>名前 <input v-model="localName"></p> <p>HP <input size="5" v-model.number="localHp"></p> </div>`, props: { name: String, hp: Number }, computed: { localName: { get: function() { return this.name }, set: function(val) { this.$emit('update:name', val) } }, localHp: { get: function() { return this.hp }, set: function(val) { this.$emit('update:hp', val) } } } });
名前とHPを変更する2つのインプットフォームに値を入力すると、親が保持する対応データが同期する。