echo("備忘録");

IT技術やプログラミング関連など、技術系の事を備忘録的にまとめています。

【Vue.js】v-forループでduplicated key警告が出る場合の対処法

※ 2021/12/10 19:00 一部内容を訂正しました

はじめに

前回に続き、今回もVue.jsネタを。

配列などの値を<template>内で表示する場合、v-forなどのループを用いることは多いと思います。

そしてその際、配列のキーとなる値(≒key)が重複している値があると「duplicated key(=キーの重複)」警告が出ます。(エラーではない)

ただしこれがなぜか「扱う値に重複がないはずなのに出る」ケースがあります。

今回はそれについての原因と対応策です。

値が重複している

まず大前提として、配列内のkey値が重複している場合、それを<template>内でv-forループさせると「duplicated key」警告が出ます。

<template>
  <div id="app">  
    <input type="button" value="sample" @click="sampleFunc">
    <ul>
      <!-- sampleFuncが実行された際、id=3が重複するので、duplicated keyが発生する -->
      <li v-for="item in items" :key="item.id">{{ item.value }}</li>
    </ul>
  </div>
</template>  
  
<script>
export default {
  data: function () {
    return {
      items: [
        {
          id: 1,
          value: 'hoge'
        },
        {
          id: 2,
          value: 'fuga'
        },
        {
          id: 3,
          value: 'piyo'
        },
      ],
    }
  },
  
  methods: {
    sampleFunc: function() {  
      // 重複した値を挿入する
      this.items.push(
        {
          id: 3,
          value: 'piyo'
        }
      );
    },
  }
}
</script>

これに関しては言うまでもなく「keyの値を重複させない」ようにすればよいです。
※よくあるのは「配列の初期化し忘れ」です。(といっても、意外と忘れがち...)

// こんな感じで、再設定前に一度初期化してあげる
items.splice(0, items2.length);

本題

で、次が本題。

下記のようなソースがあったとします。

<template>
  <div id="app">  
    <input type="button" value="sample" @click="sampleFunc">
    <!-- itemsとitems2は配列もspanタグも別物...のはずなのに、これもduplicated keyが発生する -->
    <span v-for="item in items" :key="item.id">{{ item.value }}</span>
    <span v-for="item2 in items2" :key="item2.id">{{ item2.value }}</span>
  </div>
</template>  
  
<script>
export default {
  data: function () {
    return {
      items: [
        {
          id: 1,
          value: 'hoge'
        },
        {
          id: 2,
          value: 'fuga'
        },
        {
          id: 3,
          value: 'piyo'
        },
      ],
      items2: [
        {
          id: 3,
          value: 'foo'
        },
        {
          id: 4,
          value: 'bar'
        },
      ],
    }
  }
}
</script>

itemsとitems2は配列もspanタグも別物...のはずなのに、<template>内のコメントで書いたように、これもduplicated keyが発生してしまいます。

なぜかというと、Vue.jsのv-forループのkeyは、「ある要素の子要素内全体で重複があってはならない」からです。

つまり上記ソースの例では、<div id="app">の子要素である<span>2つ(と<input>)のすべてで、同じ値のkeyがあってはならないのです。(itemsとitems2両方にid=3があるため、duplicated keyが発生する)

ここは盲点&面倒くさい点だと思います。

対策方法

対策方法ですが、とりあえず以下の2つくらいでしょうか?

  • keyにindexを使う
  • かぶらない値を設定する

keyにindexを使う
v-forのindex(=配列のindex)を使えば、とりあえずduplicated keyは回避できます。
しかしindexは配列要素の追加や削除で変わるので(=indexに対応する値が変わる)、あまりお勧めできません。

【2021/12/10 19:00追記】
indexを使うと必ず0始まりになるので、むしろduplicated keyが頻発してしまいますね。失礼しました。

なおduplicated keyが起こらないケースでも「indexは配列要素の追加や削除で変わるので(=indexに対応する値が変わる)、あまりお勧めできない」という事は変わりません。

【参考ページ】

かぶらない値を設定する
「かぶらない値を設定する」ですが、まず実際にDBなどに保存している値レベルで対応するのは困難だと思います。(0, 1, 2...など、各テーブル単位で数値などを設定するのが多いと思うので)

そうなると...なんかイケてない感がありますが、こんな感じにkey名を無理やり一意にする...しかないのかなあ、という感じです。

<template>
  <div id="app">  
    <input type="button" value="sample" @click="sampleFunc">
    <!-- JavaScriptは同じなので省略 -->
    <!-- keyを無理やり絶対に重複しない値にする -->
    <span v-for="item in items" :key="`items-${item.id}`">{{ item.value }}</span>
    <span v-for="item2 in items2" :key="`items2-${item2.id}`">{{ item2.value }}</span>
  </div>
</template>  

github.com

なんかいい方法ないかなあ、という感じです。(Vue.js3.xではどうなんだろう)

短めですが、今回はここまで。