タイトル: for 繰り返し処理
SEOタイトル: Vue.js v-for 繰り返し処理完全ガイド
この本文には実行可能なタグ等が含まれるため、安全のためHTMLソースとして表示しています。本文データは変更していません。
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;background:#fffbea;border:1px solid #f4c430;margin:1em 0;width:100%"><tr><td style="background:#f4c430;color:#000;font-weight:bold;padding:8px">この記事の要点</td></tr><tr><td style="padding:10px"><ul><li><strong>v-for</strong> は Vue.js の繰り返しディレクティブ。配列・オブジェクト・数値・文字列に対応</li><li>基本構文: <code><li v-for="item in items" :key="item.id"></code></li><li>インデックスも取れる: <code>v-for="(item, index) in items"</code></li><li><strong>:key 必須</strong>。一意な ID を渡さないと再レンダリングで挙動が崩れる</li><li><strong>v-for と v-if の同一要素</strong>は警告 → <code><template v-for></code> で囲んで分離</li></ul></td></tr></table>
<h2>v-for の基本</h2>
<p>Vue.js の <strong>v-for</strong> ディレクティブは、配列やオブジェクトなどの反復可能なデータを使って DOM 要素を繰り返し描画します。Vue 2 / Vue 3 ともに最も使用頻度の高いディレクティブの一つです。</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><template>
<ul>
<li v-for="fruit in fruits" :key="fruit">{{ fruit }}</li>
</ul>
</template>
<script setup>
const fruits = ['Apple', 'Banana', 'Cherry'];
</script></code></pre>
<p>レンダリング結果:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><ul>
<li>Apple</li>
<li>Banana</li>
<li>Cherry</li>
</ul></code></pre>
<h2>インデックスを取得</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><ul>
<li v-for="(fruit, index) in fruits" :key="index">
{{ index }}: {{ fruit }}
</li>
</ul>
<!--
0: Apple
1: Banana
2: Cherry
--></code></pre>
<h2>オブジェクトの反復</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><script setup>
const user = {
name: 'Alice',
age: 30,
job: 'Developer',
};
</script>
<template>
<!-- 値のみ -->
<ul>
<li v-for="value in user">{{ value }}</li>
</ul>
<!-- 値, キー -->
<ul>
<li v-for="(value, key) in user">{{ key }}: {{ value }}</li>
</ul>
<!-- 値, キー, インデックス -->
<ul>
<li v-for="(value, key, index) in user" :key="key">
[{{ index }}] {{ key }} = {{ value }}
</li>
</ul>
</template></code></pre>
<h2>数値範囲・文字列の反復</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!-- 1〜10 -->
<span v-for="n in 10" :key="n">{{ n }} </span>
<!-- 1 2 3 4 5 6 7 8 9 10 -->
<!-- 文字列の各文字 -->
<span v-for="ch in 'Hello'" :key="ch">{{ ch }}.</span>
<!-- H. e. l. l. o. --></code></pre>
<h2>:key の重要性</h2>
<p>Vue は <strong>:key</strong> を使ってリスト内の各要素を識別します。これがないと「リスト要素が並び替わったとき」「真ん中に挿入されたとき」に Vue が DOM 再利用に失敗し、入力欄の値がズレる・アニメーションが破綻するといった問題が起きます。</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!-- ❌ 不適: 配列インデックスを key にすると、insert/delete で挙動が崩れる -->
<li v-for="(item, idx) in items" :key="idx">
<!-- ✅ 正解: 一意な ID を使う -->
<li v-for="item in items" :key="item.id">
<!-- ✅ 文字列リストで ID が無いなら値自身でも OK (重複しない前提) -->
<li v-for="tag in tags" :key="tag"></code></pre>
<h2>v-for と v-if の組み合わせ警告</h2>
<p><strong>同一要素で v-for と v-if を使うのは非推奨</strong>です。Vue 3 では v-if が優先され、v-for で展開された個別要素を見ないため意図しない挙動になります。</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><!-- ❌ NG: 同一要素 -->
<li v-for="todo in todos" v-if="!todo.done" :key="todo.id">
{{ todo.text }}
</li>
<!-- ✅ template でラップ (Vue 3) -->
<template v-for="todo in todos" :key="todo.id">
<li v-if="!todo.done">{{ todo.text }}</li>
</template>
<!-- ✅ computed でフィルタ (推奨) -->
<li v-for="todo in activeTodos" :key="todo.id">{{ todo.text }}</li>
<script setup>
import { computed } from 'vue';
const todos = ref([...]);
const activeTodos = computed(() => todos.value.filter(t => !t.done));
</script></code></pre>
<h2>ネストした v-for</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><script setup>
const categories = [
{ name: 'Fruit', items: ['Apple', 'Banana'] },
{ name: 'Vegetable', items: ['Carrot', 'Potato'] },
];
</script>
<template>
<div v-for="cat in categories" :key="cat.name">
<h3>{{ cat.name }}</h3>
<ul>
<li v-for="item in cat.items" :key="item">{{ item }}</li>
</ul>
</div>
</template></code></pre>
<h2>複数要素をラップ (<template v-for>)</h2>
<p>1 つの v-for で<strong>複数のタグ</strong>を出力したい場合は <code><template></code> を使います (template タグは DOM に出力されません):</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><dl>
<template v-for="user in users" :key="user.id">
<dt>{{ user.name }}</dt>
<dd>{{ user.email }}</dd>
</template>
</dl></code></pre>
<h2>computed でフィルタ・ソート</h2>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-html"><script setup>
import { ref, computed } from 'vue';
const users = ref([
{ id: 1, name: 'Alice', age: 30 },
{ id: 2, name: 'Bob', age: 25 },
{ id: 3, name: 'Carol', age: 35 },
]);
const sortedUsers = computed(() =>
[...users.value].sort((a, b) => a.age - b.age)
);
const adultUsers = computed(() =>
users.value.filter(u => u.age >= 30)
);
</script>
<template>
<h3>Sorted by age</h3>
<ul>
<li v-for="u in sortedUsers" :key="u.id">{{ u.name }} ({{ u.age }})</li>
</ul>
</template></code></pre>
<p>配列を <strong>computed の中でコピー</strong> (<code>[...arr]</code>) してから sort することで、reactive の元配列を破壊しません。</p>
<h2>Vue 2 と Vue 3 の v-for の違い</h2>
<table border="1" cellpadding="6" cellspacing="0" style="border-collapse:collapse;width:100%">
<thead><tr style="background:#fff3e0"><th>項目</th><th>Vue 2</th><th>Vue 3</th></tr></thead>
<tbody>
<tr><td>v-for と v-if の優先度</td><td>v-for が優先</td><td>v-if が優先</td></tr>
<tr><td>:key の必須度</td><td>推奨</td><td>必須 (デフォルト警告)</td></tr>
<tr><td>$index, $key (暗黙変数)</td><td>非推奨</td><td>削除</td></tr>
<tr><td>v-bind スコープ</td><td>親と同じ</td><td>反復子も参照可</td></tr>
</tbody>
</table>
<h2>非破壊的に配列を変更</h2>
<p>Vue 2 の頃は <code>Vue.set()</code> や <code>this.$set</code> が必要なケースがありましたが、Vue 3 は Proxy ベースで<strong>配列のあらゆる変更を検知</strong>します:</p>
<pre style="background:#1e293b;color:#e2e8f0;padding:1em;border-radius:6px;overflow-x:auto;line-height:1.5"><code class="language-javascript">import { ref } from 'vue';
const items = ref([1, 2, 3]);
// すべて自動でリアクティブ
items.value.push(4);
items.value.pop();
items.value.splice(1, 0, 99);
items.value[0] = 100; // ← Vue 3 では検知される
items.value.sort();
items.value = items.value.filter(x => x > 1);</code></pre>
<h2>FAQ</h2>
<p><strong>Q: :key にオブジェクトを渡しても良いですか</strong><br />
A: ダメです。<strong>プリミティブ (文字列/数値)</strong> を渡してください。オブジェクトはハッシュキーが一意でなく Warning が出ます。</p>
<p><strong>Q: v-for をパフォーマンス上の理由で避けたい</strong><br />
A: 仮想スクロール (vue-virtual-scroller) を使うと、1 万件でも 50 件分だけ描画するので高速です。</p>
<p><strong>Q: Vue 2 の <code>this.$set(this.items, idx, val)</code> はもう不要</strong><br />
A: Vue 3 では完全に不要です。直接代入で OK。</p>