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

# はじめに

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


# v-modelの動作

[公式のリファレンス](https://jp.vuejs.org/v2/guide/components.html#%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%A7-v-model-%E3%82%92%E4%BD%BF%E3%81%86)にある通り、v-modelはv-onとv-bindをまとめて一行で書くためのシュガーシンタックスです
つまり以下の二行は同じ動作をします

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

※上記はテキストボックス `<input type="text">` での例です。
[公式リファレンス](https://vuejs.org/v2/guide/forms.html#Basic-Usage)にあるように、チェックボックス、ラジオボタン、セレクトボックスの場合はプロパティが異なります。
表にまとめると、次のようになります。

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

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


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

カスタムコンポーネントに対してv-modelを利用した場合、デフォルトでは`value`というpropsと`input`のイベントが使われます。
この値はカスタムコンポーネント側の定義で[変更することが出来ます](https://jp.vuejs.org/v2/api/#model)

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

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

```正しいコード１.vue
<input v-model="searchText">
<script>
data(){
  return {
    innerSearchText: ''
  }
},
computed: {
  searchText: {
    get () {
      return this.innerSearchText
    },
    set (value) {
      this.innerSearchText = value
    }
  }
}
</script>
```

```正しいコード２.vue
<input :value="searchText" @change="onChange">
<script>
data(){
  return {
    searchText: ''
  }
}
</script>

```

v-modelとsetの組み合わせは強力で、[Vuexなどと組み合わせた場合にも力を発揮します](https://vuex.vuejs.org/ja/guide/forms.html#%E5%8F%8C%E6%96%B9%E5%90%91%E7%AE%97%E5%87%BA%E3%83%97%E3%83%AD%E3%83%91%E3%83%86%E3%82%A3)


## 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には[モディファイヤが用意されていて](https://jp.vuejs.org/v2/guide/forms.html#%E4%BF%AE%E9%A3%BE%E5%AD%90)これが便利なので（特にnumber）、
適材適所で使っていけると良いかなと思います