【Vue.js】v-forで配列やオブジェクトの各要素をループして表示する方法

配列のデータを元に、リストやテーブルの行を繰り返し表示するのがv-forです。基本は簡単ですが、keyの付け方やv-ifとの組み合わせで、思わぬ挙動になることがあります。この記事では、Vue 3(<script setup>)を前提に、基本から注意点までを動くコードで整理します。

スポンサーリンク

配列をループして表示する

item in itemsの形で書くと、配列の要素を1つずつ取り出して、そのタグを繰り返します。:keyには、要素ごとに固有の値(ここではid)を渡します。

Vue:配列のv-for
<script setup>
import { ref } from 'vue'

const items = ref([
  { id: 1, text: 'Learn Vue.js' },
  { id: 2, text: 'Build a project' },
  { id: 3, text: 'Master Vue.js' }
])
</script>

<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.text }}
    </li>
  </ul>
</template>

インデックスを使う

カッコで囲んで(item, index)と書くと、現在の番号も取得できます。分割代入も使えます。inの代わりにofと書いても同じ意味です。

Vue:インデックスと分割代入
<template>
  <!-- (要素, インデックス) の順 -->
  <li v-for="(item, index) in items" :key="item.id">
    {{ index }}: {{ item.text }}
  </li>

  <!-- 分割代入も使える。in の代わりに of でもよい -->
  <li v-for="{ id, text } of items" :key="id">{{ text }}</li>
</template>

オブジェクトをループする

オブジェクトも同じv-forで繰り返せます。引数の順番は値、キー、インデックスです。

Vue:オブジェクトのv-for
<script setup>
import { reactive } from 'vue'

const user = reactive({ name: '田中', age: 30, city: '東京' })
</script>

<template>
  <!-- 値だけ表示する(keyにはキー名を使う) -->
  <li v-for="(value, key) in user" :key="key">{{ value }}</li>

  <!-- (値, キー, インデックス) の順 -->
  <li v-for="(value, key, index) in user" :key="key">
    {{ index }}. {{ key }}: {{ value }}
  </li>
</template>

数を指定して繰り返す

v-forには整数も渡せます。n in 10は1から10までの値を取り出します。0からではない点に注意してください。

Vue:範囲のv-for
<template>
  <!-- 1から10までの数が出る(0始まりではない) -->
  <span v-for="n in 10" :key="n">{{ n }}</span>
</template>

入れ子のv-for

二次元のデータは、v-forを入れ子にして表示します。内側のkeyも、外側と同様に固有の値にします。

Vue:入れ子のv-for
<script setup>
const table = [
  { id: 'r1', cells: [{ id: 'a', v: 1 }, { id: 'b', v: 2 }] },
  { id: 'r2', cells: [{ id: 'c', v: 3 }, { id: 'd', v: 4 }] }
]
</script>

<template>
  <div v-for="row in table" :key="row.id">
    <span v-for="cell in row.cells" :key="cell.id">{{ cell.v }}</span>
  </div>
</template>

templateで複数の要素を繰り返す

1回の繰り返しで複数の要素を出したいときは、<template v-for>で囲みます。<template>自体はDOMに出力されません。このときkeyは<template>側に付けます。

Vue:template v-for
<ul>
  <!-- template を使うと、1回の繰り返しで複数の要素を出せる -->
  <!-- key は template 側に付ける -->
  <template v-for="item in items" :key="item.id">
    <li>{{ item.text }}</li>
    <li class="divider" role="presentation"></li>
  </template>
</ul>

keyにはインデックスを使わない

keyは、Vueが各要素を識別して、画面を効率よく更新するための目印です。使える値は文字列か数値で、オブジェクトは使えません。そして、配列のインデックスをkeyにするのは避けてください。先頭への追加や並べ替えで、要素とインデックスの対応がずれ、入力欄の中身などの状態が別の行に残ることがあるためです。データごとに固有のidを使います。

Vue:keyにindexを使うとずれる
<script setup>
import { ref } from 'vue'

const list = ref([
  { id: 1, text: 'A' },
  { id: 2, text: 'B' },
  { id: 3, text: 'C' }
])

function addFirst() {
  list.value.unshift({ id: Date.now(), text: '新しい項目' })
}
</script>

<template>
  <button @click="addFirst">先頭に追加</button>

  <!-- NG:index を key にすると、並びが変わったときに入力欄の中身などがずれる -->
  <div v-for="(item, index) in list" :key="index">
    {{ item.text }} <input type="text">
  </div>

  <!-- OK:データごとに固有の id を key にする -->
  <div v-for="item in list" :key="item.id">
    {{ item.text }} <input type="text">
  </div>
</template>

v-ifと同じ要素に書かない

Vue 3では、同じ要素にv-forとv-ifを書くとv-ifが先に評価されます。そのため、v-ifの式でv-forの変数を使うと、「todoが未定義」というエラーになります。<template v-for>で囲むか、computedで絞り込みます。

Vue:v-forとv-ifの組み合わせ(NGとOK)
<!-- NG:同じ要素に v-for と v-if を書く
     Vue 3 では v-if が先に評価されるため、todo が未定義というエラーになる -->
<li v-for="todo in todos" v-if="!todo.isComplete">
  {{ todo.name }}
</li>

<!-- OK①:template で囲み、中の要素に v-if を付ける -->
<template v-for="todo in todos" :key="todo.id">
  <li v-if="!todo.isComplete">{{ todo.name }}</li>
</template>
Vue:computedで絞り込む
<script setup>
import { ref, computed } from 'vue'

const todos = ref([
  { id: 1, name: '買い物', isComplete: false },
  { id: 2, name: '掃除', isComplete: true }
])

// OK②:computed で先に絞り込む(こちらがおすすめ)
const activeTodos = computed(() => todos.value.filter(t => !t.isComplete))
</script>

<template>
  <ul>
    <li v-for="todo in activeTodos" :key="todo.id">{{ todo.name }}</li>
  </ul>
</template>

v-ifの基本はv-ifで条件分岐させる方法をご覧ください。

コンポーネントをv-forで繰り返す

コンポーネントにもv-forが使えます。ただし、コンポーネントのスコープは独立しているため、繰り返しのデータは自動では渡されません。propsで渡します。keyも必ず付けます。

Vue:コンポーネントのv-for
<!-- コンポーネントはスコープが独立しているので、データは props で渡す -->
<TodoItem
  v-for="(todo, index) in todos"
  :todo="todo"
  :index="index"
  :key="todo.id"
/>

配列を更新したときの反応

配列を変更すると、画面も自動で更新されます。push、pop、shift、unshift、splice、sort、reverseのように元の配列を変更するメソッドは、そのまま使えます。filterのように新しい配列を返すメソッドは、結果で置き換えます。

Vue:配列の更新
<script setup>
import { ref } from 'vue'

const items = ref([{ id: 1, text: 'A' }, { id: 2, text: 'B' }])

// 元の配列を変更するメソッド(push・pop・shift・unshift・splice・sort・reverse)は
// そのまま画面に反映される
items.value.push({ id: 3, text: 'C' })

// 元の配列を変更しないメソッド(filter など)は、結果で置き換える
items.value = items.value.filter(item => item.id !== 2)
</script>

computedの中でreverse()やsort()を直接呼ぶと、元の配列を変更してしまいます。先に[...numbers]のようにコピーしてから使ってください。

関連記事

よくある質問(FAQ)

Qkeyにインデックスを使ってはいけませんか?
A避けてください。先頭への追加や並べ替えで、要素とインデックスの対応がずれ、入力欄の中身などが別の行に残ることがあります。データごとに固有のidを使ってください。
Qv-forとv-ifを一緒に使うとエラーになるのはなぜですか?
AVue 3では同じ要素に書くとv-ifが先に評価されるためです。v-forの変数がまだ存在せず、未定義エラーになります。computedで絞り込むか、<template v-for>で囲んでください。
Qオブジェクトの(value, key)の順番がわかりません。
A値、キー、インデックスの順です。配列の場合の(要素、インデックス)とは違い、キーが2番目に入ります。
Qn in 10 は0から始まりますか?
Aいいえ、1から10までです。0始まりが必要なときは、インデックスを使うか、式で調整してください。

まとめ

v-forは、配列、オブジェクト、数のどれでも繰り返せます。keyには固有のidを使い、インデックスは避けます。v-ifとの併用はcomputedで絞り込む形にすると、エラーも防げて読みやすくなります。