【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を先に紹介したのはこのためです。

そもそもv-modelv-bindv-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の書き方は省略形だったということがわかります。computedsetを追加することで、代入されるときの挙動を指定できます。ここで、代入対象は親のvなので、emitして送信しています。

v-modelなので、親の値が更新されれば子の値も自動的に反映されます。

See the Pen vue props v-model2 by Totori (@souki202) on CodePen.

まとめ

v-modelを利用すると、子から親の値を少しだけ楽に更新することができます。子が親に対して値を渡すという関係はあまり良いとは言えません (is-aではなくhas-a) が、入力欄を子コンポーネント化し、親で送信する場合など、いくつか使う場面はあります。

v-modelv-bind@updateが合わさったものという認識を持つと、emitで可能になる理由がわかると思います。

vue v-model thumb

役に立ったらシェアしよう!