vue.jsでv-forディレクティブを使う際にv-bind:keyを設定するとどのような効果や変化があるのかわからない人も多いかと思います。特にVue.jsを使い始めた人であればv-bind:keyを設定しなければエディターに警告が表示されるからと意味もわからずに設定する人も多いのではないでしょうか。マニュアルを読んでもわからない時におすすめなのがChomeのデベロッパーツールを利用することです。デベロッパーツールを確認すると視覚的に要素の動きがわかるのでv-bind:keyを理解したい人はぜひ参考にしてみてください。

Vue2, Vue3で動作確認を行なっています。

テーブルの削除に利用

v-bind:keyを使用しない場合

v-forディレクティブを使って作成したテーブルの行削除を通してv-bind:keyの動作確認を行います。v-bindという記法ではなくv-bindを省略して:keyと記述することもできます。

最初はv-bind:keyの設定を行わずに行の削除を行うためtr要素にv-bindの設定は入っていません。


<div id="app">	
	<table class="table table-bordered">
		<thead  class="thead-dark">
			<tr>
				<th>ID</th>
				<th>ユーザ名</th>
				<th>削除</th>
			</tr>
		</thead>
		<tbody>
			<tr v-for="(user,index) in users">
				<td>@{{ user.id }}</td>
				<td>@{{ user.email }}</td>
				<td><button class="btn btn-danger" v-on:click="deleteRow(index)">削除</td>
			</tr>
		</tbody>
	</table>   
</div>

Vueインスタンスの中にメソッドdeleteRowを追加します。削除ボタンを押した行のindexを受け取り、そのindex行の削除を行っているだけのシンプルなコードです。データは独自に作成したバックエンドサーバから取得しています。このままのコードを利用しても動作確認を行うことはできません。実際に手元で動作確認をしたい場合は”Vue3でindex.htmlでコードを作成”を参考にしてください。

this.$delete(this.user,index)はthis.users.splice(index,1)と同じ意味で、this.usersのindex番目の要素の削除を行います。Vue3ではthis.$deleteは利用できません。
fukidashi

var app = new Vue({
  el: '#app',
  data: {
    users: []
  },
  methods: {
  	deleteRow : function(index){
      this.$delete(this.users,index)
  	}
  },
  mounted: function(){
    axios.get('/api/users').then(response => this.users = response.data);
  }
})
使用するテーブル
使用するテーブル

Chromeのデベロッパーツールを開いてElementsタブで下記のようにtdの要素が見えるように展開してください。

デベロッパーツールでtr, tdの要素を展開
デベロッパーツールでtr, tdの要素を展開

先頭の行の削除ボタンを押すと下記に色が付いている部分で更新が行われるのがわかります。行の削除を行うとブラウザ上では行が消えますが、実際には先頭の要素が削除されるのではなく下の行情報によって上書きされていることが確認できます。

先頭の行を削除
先頭の行を削除

Vue3でindex.htmlでコードを作成

index.htmlファイルをローカルに作成して動作確認を行いたい場合は下記のコードを利用してください。Vue3のOptions APIで設定を行なっています。


<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>   
    <title></title>
  </head>
  <body>
    <div id="app">	
      <table class="table table-bordered">
        <thead  class="thead-dark">
          <tr>
            <th>ID</th>
            <th>ユーザ名</th>
            <th>削除</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="(user,index) in users">
            <td>{{ user.id }}</td>
            <td>{{ user.email }}</td>
            <td><button class="btn btn-danger" v-on:click="deleteRow(index)">削除</td>
          </tr>
        </tbody>
      </table>   
    </div>
    <script>
      const { createApp } = Vue

      createApp({
        data() {
          return {
            users:[]
          }
        },
        methods:{
          deleteRow(index){
            this.users.splice(index,1)
          }
        },
        mounted(){
          axios.get('https://jsonplaceholder.typicode.com/users').then(response => this.users = response.data);
        }
      }).mount('#app')
    </script>
  </body>
</html>

スタイルの設定を行なっていないのでブラウザで確認すると以下のような表示となります。

index.htmlファイルで動作確認
index.htmlファイルで動作確認

v-bind:keyを使用する場合

今度は、v-forディレクティブにv-bind:keyをつけて削除をおこなってみましょう。


<tr v-for="(user,index) in users v-bind:key="user.id">

削除ボタンを押すとブラウザ上では押した行の削除が行われます。デベロッパーツールを見るとv-bindを設定した場合は先程のようにtd要素に色がつくことはなくボタンを押したtr要素のみが削除されます。各行でデータの上書きは行われていないことがわかります。

v-bindを設定して削除
v-bindを設定して削除

ここまで動作を簡単なイラストで書くとv-bind:keyがない場合は、各行の器となるtr要素は残ったまま、その器に入っているtdの中身だけ移動しており、v-bind:keyがある場合は、tr要素の器そのものが消えます。

v-bind:keyのある無しの違い
v-bind:keyのある無しの違い

ここまでの比較でv-bindの有無により、要素の削除と要素の上書きの差があることがわかりました。しかし、デベロッパーツールで確認しない限りはブラウザ上では両者の差はわかりません。では、なぜv-bind:keyをつける必要があるのでしょう。inputの要素を追加することでv-bind:keyをつけなかった場合の問題がはっきりとわかります。

input要素を使用

v-bindを利用しない場合

先ほど使用していたテーブルに新たにinput要素の列の追加を行います。


<table class="table table-bordered">
	<thead  class="thead-dark">
		<tr>
			<th>ID</th>
			<th>ユーザ名</th>
			<th>入力</th>
			<th>削除</th>
		</tr>
	</thead>
	<tbody>
		<tr v-for="(user,index) in users">
			<td>@{{ user.id }}</td>
			<td>@{{ user.name }}</td>
			<td><input type="text" class="form-control"></td>
			<td><button class="btn btn-danger" v-on:click="deleteRow(index)">削除</td>
		</tr>
	</tbody>
</table>
テーブルにinput要素を追加
テーブルにinput要素を追加

追加したinput要素に文字列を入力します。

input要素に文字列の入力
input要素に文字列の入力

入力した状態のままinput要素に入力を行ったID=3の行の削除ボタンをクリックします。inputのセルも行と一緒に削除されるものだろうと予想したと思うのですが、inputの要素だけ残ったままの状態になります。

行の削除を行う
行の削除を行う

デベロッパーツールの力を借りて、どのような変化が起きているか確認します。削除ボタンを押すとinput要素以外のtd要素には色が付いており更新が行われるのがわかりますがinput要素はそのままの状態で変化がないということがわかります。v-bind:keyをなしの状態でinput要素を使うと不具合の原因になることがわかりました。

デベロッパーツールで削除を確認
デベロッパーツールで削除を確認

v-bindを利用する場合

最後にv-bind:keyを設定するとinput要素に入力した文字列がどうなる確認します。


<tr v-for="(user,index) in users" v-bind:key="user.id">
input要素に文字列の入力
input要素に文字列の入力

削除ボタンを押すとブラウザ上からもinput要素も一緒に削除されることが確認できます。

削除ボタンを押した結果
削除ボタンを押した結果

デベロッパーツールを見てもtrの要素が削除されるのが確認できます。

v-bind:keyをつけることでinput要素の不具合が解消されることが確認できました。

v-bind:key=”index”の場合は

v-bind:key=”index”を設定して実施するとkeyの設定しない場合と結果は同じになります。v-bind:keyではindexの利用ではなく、idなどその要素を一意に識別できる要素を設定する必要があることがわかります。


<tr v-for="(user,index) in users" v-bind:key="index">

以下はマニュアルからのv-bind:keyの設定の抜粋になりますが、もう一度読み直して今回の文書が少しでも理解の助けになればと思います。正直なところ動作確認をせず下を読んでも意味がさっぱり分かりませんでした。

マニュアルからの抜粋
マニュアルからの抜粋