見出し画像

【Vue.js】v-forを使う時の注意点 key属性をつけ忘れないようにする

はじめに

こんにちは、SHIFT の開発部門に所属している Katayama です。

今回はフロントエンドに関する記事を書いていこうと思います。 Vue.js ではいくつか仕様上、実装時には注意が必要なものがありますが、今回はその中でも v-for ディレクティブを使う時の仕様上の注意事項を見ていきたいと思います。

v-for を使う時の注意点  key 属性をつけ忘れない

v-for ディレクティブを使う際には、"key 属性" を付与する事で予期せぬバグが発生しないように注意する必要がある。

※ディレクティブとは、Vue.jsで使えるv-から始まる特別な属性のことで、詳細はディレクティブの項を参照。また、v-forはリストレンダリング(配列に基づいてアイテムのリストを描画する)のに使われるディレクティブであり、複数の要素をリスト形式でHTML上に描画する際に使うと便利な機能。詳細はリストレンダリングを参照。

key 属性のつけ忘れると、以下のように、1 度『先頭を削除ボタン』をクリックしたのにもかかわらず、テキストボックスの部分は"Apple"のままになるという事が起きる。

動画のソースコードは以下。

<!-- sample.html -->
<body>
  <div id="app">
    <ul>
      <div v-for="fruit in fruits">
        <p>{{ fruit }}</p>
        <input type="text" />
      </div>
    </ul>
    <button @click="remove">先頭を削除</button>
  </div>

  <script src="https://cdn.jsdelivr.net/npm/vue@2.6.14/dist/vue.js"></script>
  <script>
    new Vue({
      el: "#app",
      data: {
        fruits: ["りんご", "バナナ", "ぶどう"],
      },
      methods: {
        remove: function () {
          this.fruits.shift();
        },
      },
    });
  </script>
</body>

ここで意図している動きとしては、ラベル・テキストボックスの"Apple"がそれぞれ消えて、上から順に「Banana、Grape」とラベル・テキストボックスが表示されている状態になる事。しかし実際には上記の動画のようになってしまう。

今回このような動きになってしまった原因としては、v-for ディレクティブには、

・要素の移動を最小限に抑えるアルゴリズムが採用されており、可能な限りその場の近い同じ要素を再利用しようとする

という性質(状態の維持)があるため。今回だと、配列の要素を消す前後の差分を考えて効率よくレンダリングしようとした結果、テキストボックスの部分(input タグ)は DOM 上は同じなので削除されず、"Apple"と入力されたテキストボックスがそのまま残り続けてしまったため、上記の動画のような挙動になってしまった。

この事象を解決するには、

・key 属性を付与し、要素とデータとを関連付ける(紐づける)

という事をすればいい。今回で言えばラベル・テキストボックスのまとまり(div タグという要素)に対して「fruits 配列の 1 つ 1 つのデータ」とを関連付けるようにする。そうすると、ラベル・テキストボックスのまとまりがそれぞれ別のものであり、再利用できるものではないとVue.jsに伝える事ができ(同じ要素として判定されず再利用されず)、結果として以下のように期待通りの動きになる。

改善後のソースコード(抜粋)は以下。

<!-- sample.html 変更後(一部抜粋)-->
<body>
  ・・・
  <div v-for="fruit in fruits" :key="fruit">
    <p>{{ fruit }}</p>
    <input type="text" />
  </div>
  ・・・
</body>

※key 属性を使用する時の注意点

・template タグでは使えない
 理由:template タグには属性は存在しないため
・v-for の index を key 属性に指定しない

理由:リストレンダリングする時のリストの中身が変化しても、リストの中身が変化するとそのインデックスも変化し、同じ index になってしまう場合があるから(例えば今回の fruits 配列で言えば、先頭の「りんご」を削除すると、「バナナ」のインデックスが 0 になってしまい、削除の前後で v-for の index は変化しない事になる。すると、再利用されてしまい意図した動きにならない(『おまけ』の項を参照))

※上記の注意「template タグでは使えない」は、Vue.js2.x の場合のみ。Vue.js3.x では template 内で key 属性が使えるようになっている。それについてはtemplate v-for の使用を参照。)

まとめとして

今回は Vue.js の v-for ディレクティブを使う時の注意すべきポイントについて理解を深めてみた。なかなか仕様を理解せずに使うと罠にはまるのでこういった注意点は 1 つ 1 つ抑えていく事が重要に思える。

おまけ

key 属性に index を指定するとどうなるか?

最初に見た1 度『先頭を削除ボタン』をクリックしたのにもかかわらず、テキストボックスの部分は"Apple"のままになるという事が起きる。そのため key 属性は index ではなく、配列の要素を指定する必要がある。

動画のソースコード(抜粋)は以下。

<!-- sample.html (一部抜粋)-->
<body>
  ・・・
  <div v-for="(fruit, index) in fruits" :key="index">
    <p>{{ fruit }}</p>
    <input type="text" />
  </div>
  ・・・
</body>

_________________________________

執筆者プロフィール:Katayama Yuta
SaaS ERPパッケージベンダーにて開発を2年経験。 SHIFTでは、GUIテストの自動化やUnitテストの実装などテスト関係の案件に従事したり、DevOpsの一環でCICD導入支援をする案件にも従事。 最近開発部門へ異動し、再び開発エンジニアに。座学で読み物を読むより、色々手を動かして試したり学んだりするのが好きなタイプ。

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/

みんなにも読んでほしいですか?

オススメした記事はフォロワーのタイムラインに表示されます!