760
632

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-05-13

はじめに

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)、
適材適所で使っていけると良いかなと思います

760
632
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
760
632

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?