【Vue/Nuxt】multipleなselectのデータを親子間で受け渡すには?

当記事は、半年以上前に投稿されたものです。そのため、古い技術や情報をもとに書かれている可能性があります。参照する際は十分に注意していただければです。

Vue / Nuxt で form 周りを触っていると、双方向バインディングのありがたさが身に染みるこの頃ですが、

Atomic デザインをしていると Atom / Molecules / Organisms / templates / pages にコンポーネントが徹底的に分解されていくので、 親子間でのデータの受け渡しが必須になってくるかと思います。

するとです、multiple タイプな select でどうデータを受け渡せば良いのかで詰まりました。emoji-confused

ですので、自戒もこめて下記にその方法を残しておきます。

TL; DR

以下は、オレオレ Atomic デザインで親子間を構築してますが、components をどう分割しているかだけなので、そのまま自身の環境に合わせればたぶん動くと思います。

親コンポーネント

<template>
  <SelectMultiple
    :options="options"
    :selected="selected"
    @select="$emit('select', $event)"
  />
</template>

<script>
import SelectMultiple from '@/components/Atoms/SelectMultiple';

export default {
  components: {
    SelectMultiple
  },
  props: {
    selected: {
      type: Array,
      required: true
    }
  },
  data: () => ({
    options: [
      { text: 'hoge1', value: 'hoge1' },
      { text: 'hoge2', value: 'hoge2' }
    ]
  })
</script>

子コンポーネント

<template>
  <select
    v-model="select"
    multiple
  >
    <option
      v-for="(option, key) in options"
      :key="key"
      :value="option.value"
    >
      {{ option.text }}
    </option>
  </select>
</template>

<script>
export default {
  props: {
    selected: {
      type: Array,
      required: true
    },
    options: {
      type: Array,
      required: true
    }
  },
  computed: {
    select: {
      get () {
        return this.$props.selected;
      },
      set (value) {
        this.$emit('select', value);
      }
    }
  }
};
</script>

通常の親子間の受け渡し

props と $emit

親子間でデータを受け渡す際は、

  • 親コンポーネント -> 子コンポーネント : props オプション
  • 子コンポーネント -> 親コンポーネント : $emit メソッド

ここで注意したいのが、props で渡された値は更新ができないreadonlyであることです。

v-model は シンタックスシュガー

v-model は、v-bind:value(各種入力値)と v-on:input(各種イベント)のシンタックスシュガー(糖衣構文)で、簡潔に記述するために v-model が多用されています。

ところが、親子間のデータ受け渡しには v-model ではなく、v-bind:value と v-on:input を使う方がスムーズに動きます。 これは、前述した props が読み込み専用であることに起因しています。

v-model に props で渡されたデータを入力するとエラーがおきます。 これは、読み込み専用であるデータを v-model 内で書き換えようとするために起きるエラーです。

そこで、これを回避するために、v-model を分解して、props のデータを v-bind:value に入力、v-on:input で発火したイベント処理を $emit で親に渡して、props を更新しないようにしてあげます。

<template>
  <div>
    <input v-model="value"> // ×
    <input v-bind:value="value" v-on:input="$emit('input', $event.target.value)"> // ◯
  </div>
</template>

<script>
export default {
  props: [value]
}
</script>

ちなみに、各入力タグのv-modelのシンタックスシュガーは下記のようになっています。

入力タグ シンタックシュガー
input[text], textarea v-model = v-bind:value + v-on:input
input[check], input[radio] v-model = v-bind:checked + v-on:change
select v-model = v-bind:value + v-on:change

ところが multiple な select では、、、

multiple タイプの select では複数の option の value が選択されて、それを親に渡す必要があります。 ところが、前述の select のシンタックスシュガーと $emit では、はじめに選択された option の value のみ渡されるような挙動になりました。

// ×
<template>
  <select
    :value="selected"
    :change="$emit('select', $event.target.value)"
    multiple
  >
以下略

これは、$event.target.value が原因です。 $event は、Javascript におけるイベント発生時のオブジェクトが詰まっているのですが、 $event.target.value では、はじめに選択されたオプションの value しか見れないので、結果的に複数ではなく、1つだけ渡された格好になりました。

そこで、$event のあらゆるプロパティを覗いて複数の value を渡そうとしたのでしたが、結果的に上手くはいきませんでした。(もしかしたら、何らかのプロパティを渡せば動くかも?)

そこで敢えて v-model を使うことに

この方法は、こちらの方の記事を参考に multiple な select に適用したものです。

v-model に直接 props を入力するのではなく、computed を介して、v-model の getset メソッドでデータの受け渡しを制御する方法です。

こうすることによって、複数の option の value が親に渡せることが確認できました。

<template>
  <select
    v-model="select"
    multiple
  >
    <!-- 略 -->
  </select>
</template>

<script>
export default {
  props: {
    selected: {
      type: Array,
      required: true
    }
  },
  computed: {
    select: {
      get () {
        return this.$props.selected;
      },
      set (value) {
        this.$emit('select', value);
      }
    }
  }
};
</script>

おわりに

ざっと、現在の私の知見としては下記のような感じです。

  • 同一コンポーネント間であれば、v-model でさくっとデータの受け渡しが可能
  • 親子コンポーネント間であれば、v-bind:(props) + v-on:(event) でデータの受け渡しが可能
  • ただし、multiple な select のように、幾つかの入力タグでは、適切な $event.target.value が見出せないので、v-model の get、set で親子間のデータ渡しを制御すること

うーん、難しいな emoji-sweat

やはり説明書を読まない、ドキュメントを読めていないツケが回ってきているような状態ですね。

参考文献


Canji

クラウド周りをちょこまかしたい注意散漫人間。個人開発を楽しんでいたあの頃。