Edited at

Vue.jsのv-modelを正しく使う


はじめに

v-modelはVue.jsを使ってフォームを構築する際によく使う機能です。

しかし、意外ときちんと使えていない場面を見かけることが多いので仕様と間違えやすいところを簡単に整理します。


v-modelの動作

公式のリファレンスにある通り、v-modelはv-onとv-bindをまとめて一行で書くためのシュガーシンタックスです

つまり以下の二行は同じ動作をします

<input v-model="searchText">

<input :value="searchText" @change="searchText = $event.target.value">


カスタムコンポーネントでのv-model

カスタムコンポーネントに対してv-modelを利用した場合、デフォルトではvalueというpropsとinputのイベントが使われます。

この値はカスタムコンポーネント側の定義で変更することが出来ます

以下は公式サンプルの引用です。

Vue.component('my-checkbox', {

model: {
prop: 'checked',
event: 'change'
},
props: {
// これによって、 `value` プロパティを別の目的で利用することを許可します
value: String,
// `value` の代わりとなるプロパティとして `checked` を使います
checked: {
type: Number,
default: 0
}
},
// ...
})


ありがちな間違い


v-modelと@changeを両方書く

先述の通りv-modelはイベントハンドリングそのものを兼ねているため、v-modelと@changeのように二重に書くとおかしなことになります。

例えば以下のようなコードです。


間違ったコード.vue

<input v-model="searchText" @change="(value) => searchText = value">


v-modelはそれ自体がchangeイベントのハンドリングも持っているため、changeに対するイベントが重複してしまいます。

もしchangeイベントで単なる代入以上の処理をしたい場合にはv−modelの利用自体を諦めて素直にv-bindとv-onを別々に書くか、searchTextの算出プロパティを使ってコードを書きます


正しいコード1.vue

<input v-model="searchText">

<script>
data(){
return {
innerSearchText: ''
}
},
computed: {
searchText: {
get () {
return this.innerSearchText
},
set (value) {
this.innerSearchText = value
}
}
}
</script>


正しいコード2.vue

<input :value="searchText" @change="onChange">

<script>
data(){
return {
searchText: ''
}
}
</script>


v-modelとsetの組み合わせは強力で、Vuexなどと組み合わせた場合にも力を発揮します


propsをそのままv-modelに渡してしまう

v-modelの変数としてpropsをそのまま橋渡しにしたくなる場合はよく出てきますが、これをやってしまうとpropsの値を直接書き換えてしまうため、警告が表示されます


間違ったコード.vue

<input v-model="searchText"> # v-modelは searchText = $event.taget.value を実行するため警告が出る。
<script>
props: {
searchText: String
}
</script>


また、これを回避しようとしてpropsの値からdataを定義してしまうのも同じく間違いです

以下のコードはそのサンプルです。


間違ったコード2.vue


<input v-model="searchTextData">
<script>
props: {
searchText: String
},
data() {
return {
searchTextData: this.$props.searchText
}
}
</script>


このコードは一見正しく動くように見えるのですが、propsの値が初期値に使われた後はdataとpropsの間で値が同期されないため、

親コンポーネントから別の値を送り込んでも反映されなくなります。

それではどうすればいいかというとこれも算出プロパティを使うと回避可能です


正しいコード.vue

<input v-model="innerSearchText">

<script>
props: {
searchText: String
},
computed: {
innerSearchText: {
get () {
return this.$props.searchText
},
set (value) {
this.$emit('change', value)
}
}
}
</script>

つまりこのコンポーネントの中で値を代入するのではなくさらに親のコンポーネントに対して値をイベントとして送り出します。

そして親のコンポーネント側でpropsの値を書き換えてやることでこのコンポーネントを正しく使うことができるようになります。

setterのかわりにwatchを使う手法もありますが、データの流れが見えづらくなるので個人的にあまりオススメしません。


余談

v-modelはサンプルコードではよく出てきますしそれぐらいのコードであれば便利なのですが、

実際のアプリケーションで値の代入だけで済むことは少なく、changeやinputイベントをキャッチしてなんらかの処理を書かないといけない場面が多いのではないかと思います。

そのためv-modelにこだわらないほうが色々とシンプルにかける場面は多いので気をつけたほうがいいかなと感じています。

ただしv-modelにはモディファイヤが用意されていてこれが便利なので(特にnumber)、

適材適所で使っていけると良いかなと思います