mt_coff's log

メモとか雑に

v-model を捨てる選択肢

v-model

Vue.js で便利な機能の一つに v-model があります。

v-model にデータを渡すと自分でフォームのイベントを利用し値を更新するということをしないで済む。

確かに便利ですが、コンポーネントがネストされていくにつれ親コンポーネントv-model を利用するために

<template>
  <input type="text" :value="value" @input="handleInput" />
</template>

<script>
  import Vue from "vue";

  export default Vue.extends({
    props: {
      value: {
        type: String
      }
    },
    methods: {
      // これより上の階層のコンポーネントの場合は
      // handleInput(value) {
      //   this.$emit("input", value)
      // }
      handleInput(event) {
        this.$emit("input", event.currentTarget.value);
      }
    }
  });
</script>

というのを実際に v-model を使用するコンポーネントまで続けることになってしまいます。

1 つ上の階層の場合 emit で親コンポーネントにわたす値が変化します。一定以上の Vue.js の経験がある。もしくはしっかりとコンポーネントの管理がされていない場合には間違った値を渡す可能性も考えられます。(v-model が 糖衣構文であることを知らない人も結構いる気がします。)

Props で管理する

emit を使用するのではなく親から関数を props で受け取り、それを子コンポーネントでは発火させることで、 emit 時のような親コンポーネントへ渡す値を考慮する必要がなくなります。

ただし、 v-model で利用できる修飾子(.number, .lazyなど)が利用できなくなるデメリットが存在します。

実際の子コンポーネントは以下の感じになるかと思います。

template>
  <input type="text" :value="value" @input="input" />
</template>

<script>
  import Vue from "vue";

  export default Vue.extends({
    props: {
      value: {
        type: String
      },
      input: {
        type: Function
      }
    }
  });
</script>

このパターンでは props を利用していることから特に props命名に縛られないため柔軟に利用できるということです。

例えば input 要素を 2 つ持つようなコンポーネントを考えると v-model を利用するためには props として value がいなければならず、 emit もそれぞれの input 要素から取得した値をなんとかまとめて親に値を渡すなどということになります。

しかし、 props のみで管理すればそれぞれの input 要素を利用する状況に応じた命名をしそれぞれ関数を渡すだけで良くなります。 子コンポーネントから渡ってくる値の形式に気を配らなくてよくなるのです。

まとめ

props で関数を渡すパターンの方が大抵の場合柔軟に利用できるのではないかと思います。

もちろんシンプルなコンポーネントのみで済むのであれば、数々の修飾子を利用できる v-model が利用可能になるパターンを使って恩恵を受けるのがいいでしょう。

変に v-model にこだわらずに捨てる選択肢を持つことで柔軟にコンポーネントを開発していきましょう。

サンプルとして簡単なものですがリポジトリを置いておきます。

github.com