Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
561
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

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

はじめに

v-modelはVue.jsを使ってフォームを構築する際によく使う機能です。
しかし、意外ときちんと使えていない場面を見かけることが多いので仕様と間違えやすいところを簡単に整理します。

v-modelの動作

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

<input v-model="searchText">
<input :value="searchText" @input="searchText = $event.target.value">

※上記はテキストボックス <input type="text"> での例です。
公式リファレンスにあるように、チェックボックス、ラジオボタン、セレクトボックスの場合はプロパティが異なります。
表にまとめると、次のようになります。

v-model を使ったときの展開のされ方

フォーム項目の種類 v-bind: v-on:
テキストボックス、テキストエリア value input
チェックボックス、ラジオボタン checked change
セレクトボックス value change

カスタムコンポーネントでの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)、
適材適所で使っていけると良いかなと思います

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
561
Help us understand the problem. What are the problem?