【vue】 v-modelを使って子から親に値を送信する
コンポーネントの章でpropsで値を渡す方法を紹介しました。ここでは、propsや値の渡し方について掘り下げていきます。
コンポーネントについては下の記事にあります。
prop復習
まずは復習として、普通にpropで値を渡します。
この場合、渡した値は変更できません。 (配列やオブジェクトの場合は参照渡しとなるため、中身を変えると親要素も連動して変わりますが、直接代入はできません。)
<div id="app"> <button-component :add-value="1"></button-component> <button-component :add-value="2"></button-component> <button-component :add-value="3"></button-component> </div>
app.component("button-component", { props: ["addValue"], data() { return { value: 0, } }, methods: { add() { this.value += this.addValue; } }, template: ` <div> <button @click="add">クリック! {{ addValue }}</button> {{ value }} </div> `, });
See the Pen vue props1 by Totori (@souki202) on CodePen.
v-modelで渡す
v-model
を使って渡すことで、親と子の値を連動させやすくなりますが、使い方を理解するにはv-model
が何をしているのか理解する必要があります。
v-model自体の動き
実体はemit
できるようにカスタムイベントを渡しているだけで、emit
を先に紹介したのはこのためです。
emitについては下の記事にあります。
そもそもv-model
はv-bind
とv-on
とまとめたもので、次の2行は同じです。
<input v-model="v"> <input :value="v" @input="v = $event.target.value">
$event.target.value
は入力された値です。inputにv-model
を指定したときの挙動は既に紹介し、入力を更新したらvue側の変数も変わると紹介しました。
これは単に参照を渡すとかそういうものではなく、中ではv-bind
で表示するのと、@input
が発火したときに代入するという2つのパーツに別れているのです。
v-modelをpropに使う
props
に値を渡すのも、同様のことが行えます。
<!-- この2行は同じ --> <button-component v-model:value="v"></button-component> <button-component :value="v" @update:value="v = $event"></button-component>
この2行も同じです、カスタムイベントで代入しているということは、値を伝えるにはthis.$emit("update:value", ...)
する必要があるということがわかります。
<button-component v-model:value="value" :add-value="1"></button-component>
app.component("button-component", { props: ["value", "addValue"], data() { return {}; }, methods: { add() { // v-model経由のpropであるvalueと、addValueを加算したものを親のvalueに入れる this.$emit("update:value", this.value + this.addValue); } }, template: ` <div> <button @click="add">クリック! +{{ addValue }}</button> </div> ` });
See the Pen vue v-model props by Totori (@souki202) on CodePen.
応用例
まずは今までの知識を活用して実際に使ってみましょう。
ここでは、入力した値と親と連携するという、比較的よく見るであろう例を紹介します。
watchなどを合わせる
入力欄が更新されると、emitを実行して親の値を更新する例です。
子のdataに親の値をコピーして、その値と親の値をemitで連動させます。
<button-component v-model:value="v"></button-component>
const app = Vue.createApp({ data() { return { v: 0 }; } }); app.component("button-component", { props: ["value"], data() { return { myValue: this.value }; }, watch: { myValue() { // v-model:value なので、update:valueで親を更新 this.$emit("update:value", this.myValue); } }, template: ` <div style="border: 1px solid #000;"> <p>コンポーネント側</p> <p>数値を入力: <input v-model.number="myValue" type="number"></p> </div> ` });
See the Pen vue props v-model by Totori (@souki202) on CodePen.
しかし、これはあまり良い例とは言えません。わざわざ子のコンポーネントに新しく変数を用意しているためです。
computedを使う
より良い例として、computed
を利用した例を紹介します。
watch
の代わりに、computed
で値をセットします。
app.component("button-component", { // ... computed: { myValue: { get() { return this.value; }, set(v) { this.$emit("update:value", v); } } }, // ... });
今までのcomputed
の書き方は省略形だったということがわかります。computed
にset
を追加することで、代入されるときの挙動を指定できます。ここで、代入対象は親のv
なので、emit
して送信しています。
より詳しいcomputedに関しては、下の記事で説明しています。
v-model
なので、親の値が更新されれば子の値も自動的に反映されます。
See the Pen vue props v-model2 by Totori (@souki202) on CodePen.
まとめ
v-modelを利用すると、子から親の値を少しだけ楽に更新することができます。子が親に対して値を渡すという関係はあまり良いとは言えません (is-a
ではなくhas-a
) が、入力欄を子コンポーネント化し、親で送信する場合など、いくつか使う場面はあります。
v-model
はv-bind
と@update
が合わさったものという認識を持つと、emit
で可能になる理由がわかると思います。
