💨

vue3でv-forとv-modelを使った複数の入力フォームバインディング

2022/08/21に公開

やりたいこと

  1. セレクトボックスで選んだ数字の分だけ入力フォームを用意する。
  2. それぞれの入力フォームを独立したものとして扱えるようにする。
  • 以下の画像のようになるのが最終目標です。
    image.png

セレクトボックスで選んだ数字の分だけ入力フォームを用意する

  • おそらくfor文とかを回せば同じようなことができるのですが、なんか力業感があってスマートじゃないので、それは避けたいですよね。
  • 色々と探していたところ以下の記事が参考になりました。
    【JavaScript】指定した回数分だけループ処理をする方法とは
  • 以下のように、セレクトボックスで選択した分だけ入力フォームが用意されるようになります。
<template>
    <div>
        <div v-for="(item, index) in list" :key="index">
          <input v-model="item.text" />
        </div>
        <div>
          <select v-model="count">
            <option v-for="index in 100" :key="index" :value="index">
              {{ index }}
            </option>
          </select>
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref, watch, type Ref } from 'vue'

const list = ref([{ text: '' }])
const item = ref({ text: '' })
const count = ref(0)

watch(count, () => {
  list.value = [...Array(count.value)].map(() => item.value)
})
</script>

image.png

それぞれの入力フォームを独立したものとして扱いたい。

  • この問題の解決にめちゃくちゃ時間がかかりました。

  • 正直心が折れかけました。

  • 今のままだと以下のような感じで全ての入力フォームが連動してしまいます。
    image.png

  • それぞれの入力フォームを別々のものとして扱いたいですよね。

    • 配列に入れる値がオブジェクトじゃなくて、プリミティブ型を入れる場合は全て独立されるのですが、オブジェクトを入れる場合はそうはいかないみたいですね。
<template>
    <div>
        <div v-for="(item, index) in list" :key="index">
          <input v-model="item.text" />
        </div>
        <div>
          <select v-model="count">
            <option v-for="index in 100" :key="index" :value="index">
              {{ index }}
            </option>
          </select>
        </div>
    </div>
</template>

<script setup lang="ts">
  import { ref, watch, type Ref } from 'vue'
  
  const list: Ref<string[]> = ref([])
  const items = ref({ text: '' })
  const count = ref(0)
  watch(count, () => {
    // ここでitems.value.textというプリミティブな値を配列に入れている
    list.value = [...Array(count.value)].map(() => items.value.text) 
  })
</script>

image.png

解決策

  • Deep copyを使うことで解決できました。
  • いつぞや聞いたdeep copyとかshallow copyの話じゃないのか?って思って調べたらビンゴでした。
  • 正直deep copyとかは配列をコピーするときだけの話かなと思っていたのですが、それだけじゃないみたいでしたね。
<template>
    <div>
        <div v-for="(item, index) in list" :key="index">
          <input v-model="item.text" />
        </div>
        <div>
          <select v-model="count">
            <option v-for="index in 100" :key="index" :value="index">
              {{ index }}
            </option>
          </select>
        </div>
    </div>
</template>

<script setup lang="ts">
import { ref, watch, type Ref } from 'vue'

const list = ref([{ text: '' }])
const item = ref({ text: '' })
const count = ref(0)

watch(count, () => {
  list.value = [...Array(count.value)].map(() => JSON.parse(JSON.stringify(item.value)))
})
</script>

image.png

まとめ

  • deep copyの機能を知っていればなんのことはない現象だと思いますが、起きている現象がわからない場合に解決までどうやって取っ掛かりを探すかって難しいですよね。
  • あの話が関係あるんじゃないかなって思ったら調べてみるのは大事ですね。

参考記事

Discussion