Vue.js v-for
でリスト表示をマスター!配列・オブジェクトの繰り返し方法を徹底解説
Web開発において、同じ構造を持つ複数の要素(リストアイテム、カード、テーブルの行など)を表示することは非常に一般的です。ブログ記事の一覧、ショッピングサイトの商品リスト、TODOリストのアイテムなど、様々な場面で利用されます。このようなリスト構造を効率的に、かつ動的に表示するために、Vue.jsでは強力なディレクティブであるv-for
が提供されています。
v-for
を使うことで、JavaScriptの配列やオブジェクトのデータに基づいて、HTML要素を繰り返し生成することができます。これにより、手動で一つずつ要素を作成する手間を省き、データの変更に自動的にUIが追従する、宣言的なコーディングが可能になります。
この記事では、Vue.jsのv-for
ディレクティブに焦点を当て、その基本的な使い方から、配列・オブジェクトの繰り返し方法、パフォーマンス最適化の鍵となるkey
属性の重要性、他のディレクティブとの組み合わせ、そして配列やオブジェクトの変更検知に関する注意点まで、包括的かつ詳細に解説します。この記事を読むことで、あなたはVue.jsを使ったリスト表示を完全にマスターし、より効率的で堅牢なアプリケーション開発ができるようになるでしょう。
この記事で学べること:
v-for
ディレクティブの基本的な構文と使い方- 配列の要素を繰り返し表示する方法 (
item
,index
) - オブジェクトのプロパティを繰り返し表示する方法 (
value
,key
,index
) - リストレンダリングにおける
key
属性の役割と重要性 v-for
とv-if
を組み合わせて条件付きリスト表示を行う方法- リストアイテムをコンポーネントとして扱う方法
- Vueが配列やオブジェクトの変更を検知する仕組みと注意点
v-for
を使った実践的なユースケースとサンプルコード- 大規模なリストを扱う際のパフォーマンス考慮事項
さあ、Vue.jsのv-for
の世界へ飛び込み込みましょう!
1. はじめに:なぜv-for
が必要なのか?
現代のフロントエンド開発において、データとUIの連携は非常に重要です。特に、バックエンドから取得したデータのリストを表示する場合など、同じレイアウト構造を何度も繰り返す必要があります。
例えば、ユーザーのリストを表示したいとします。もしv-for
のような仕組みがなければ、JavaScriptを使って配列をループし、各ユーザーデータからHTML要素を生成し、それをDOMに追加するという手続き的なコードを書くことになるでしょう。
“`javascript
// v-forがない場合のイメージ (Vueを使わない素のJSに近い考え方)
const users = [
{ id: 1, name: ‘Alice’ },
{ id: 2, name: ‘Bob’ },
{ id: 3, name: ‘Charlie’ }
];
const userListElement = document.getElementById(‘user-list’);
users.forEach(user => {
const li = document.createElement(‘li’);
li.textContent = user.name;
userListElement.appendChild(li);
});
// …もしユーザーが追加/削除されたら、またリストを再構築するコードを書く必要がある…
“`
このような手続き的なアプローチは、データの変更に対応するのが非常に面倒になります。ユーザーが一人追加されたり、削除されたりするたびに、リスト全体を更新する、あるいは変更部分だけを特定して操作する複雑なロジックが必要になります。
Vue.jsのようなモダンなフレームワークは、この問題を「宣言的UI」というアプローチで解決します。つまり、「データがこうなら、UIはこうあるべきだ」と宣言するだけで、データの変更に応じてフレームワークが自動的にUIを更新してくれます。
v-for
ディレクティブは、まさにこの宣言的なリスト表示を実現するための主要なツールです。データ配列(またはオブジェクト)を指定するだけで、Vueがそのデータに基づいて要素を繰り返し生成し、DOMに描画します。そして、元のデータが変更されると、Vueは効率的に差分を検出し、必要な部分だけを更新します。
2. Vue.jsの基本的な構成要素のおさらい
v-for
を理解するためには、Vue.jsの基本的な考え方を少しおさらいしておきましょう。
2.1 Vueインスタンスとデータ
Vueアプリケーションは、通常、new Vue({...})
(Vue 2) または createApp({...})
(Vue 3) で作成されるVueインスタンスを中心に構築されます。このインスタンスのdata
オプション(Vue 3ではsetup関数内で定義しreturnするなど)に保持されたデータは、リアクティブ(反応的)になります。つまり、そのデータが変更されると、Vueはその変更を検知し、関連するUIを自動的に更新します。
“`html
{{ message }}
“`
この例では、message
データが変更されれば、画面上の<p>
タグの内容も自動的に更新されます。
2.2 テンプレート構文とディレクティブ
VueはHTMLベースのテンプレート構文を使用します。テンプレート内では、{{ }}
のような記法を使ってデータを表示したり、v-bind
(:
) や v-on
(@
) のようなディレクティブを使ってHTML要素の属性やイベントハンドラをデータに紐付けたりします。
ディレクティブは、v-
接頭辞を持つ特別な属性です。Vueがテンプレートをコンパイルする際に、これらのディレクティブを認識し、特定の振る舞いをDOM要素に適用します。v-for
も、このディレクティブの一つです。
3. v-for
ディレクティブの基本構文
v-for
ディレクティブは、リストデータを基に要素を繰り返し描画するために使用します。最も一般的な構文は以下の通りです。
“`html
- {{ item }}
“`
このコードを実行すると、以下のHTMLが生成されます。
“`html
- リンゴ
- バナナ
- オレンジ
“`
v-for="item in items"
という部分がv-for
ディレクティブです。これは「items
配列の各要素に対して、それをitem
という変数に一時的に格納し、その格納したitem
を使ってこの<li>
要素を繰り返して生成しなさい」という意味になります。
items
: 繰り返し処理の対象となるデータ(この場合は配列)。Vueインスタンスのdata
やcomputed
プロパティなどで定義されている必要があります。item
:items
内の現在の要素を表すエイリアス(別名)。ループの各イテレーション(繰り返し処理の各回)で、items
の次の要素がitem
に代入されます。このエイリアス名は任意に決められます(例:fruit
,todo
,user
など)。
v-for
ディレクティブが記述された要素(この場合は<li>
)とその子要素({{ item }}
)は、ループの各回でテンプレートとして使用され、描画されます。
3.1 インデックスを取得する
配列を繰り返す際、現在の要素が配列の何番目の要素であるか(インデックス)が必要になることがあります。v-for
では、以下の構文を使うことでインデックスも取得できます。
“`html
- {{ index }}: {{ item }}
“`
生成されるHTML:
“`html
- 0: リンゴ
- 1: バナナ
- 2: オレンジ
“`
構文は (item, index) in items
となります。括弧 ()
で囲み、最初の引数で要素、2番目の引数でその要素のインデックスを受け取ります。インデックスは0から始まります。
このindex
は、例えばリスト番号を表示したり、特定のインデックスの要素に対してだけスタイルを適用したりする場合に便利です。
4. 配列の繰り返しを深掘り
v-for
は様々な種類の配列に対して使用できます。
4.1 プリミティブ型の配列
数値、文字列、真偽値などのプリミティブ型を要素とする配列も繰り返し表示できます。
“`html
数値リスト
- {{ number * 2 }}
真偽値リスト
- Status: {{ bool ? ‘Active’ : ‘Inactive’ }}
“`
この例では、数値配列を繰り返して各要素を2倍にして表示したり、真偽値配列を繰り返してステータス文字列に変換して表示したりしています。
4.2 オブジェクトの配列
最も一般的なユースケースは、オブジェクトを要素とする配列です。例えば、ユーザーのリスト、商品のリストなどです。
“`html
ユーザーリスト
- ID: {{ user.id }}, 名前: {{ user.name }}
“`
生成されるHTML:
“`html
- ID: 1, 名前: Alice
- ID: 2, 名前: Bob
- ID: 3, 名前: Charlie
“`
オブジェクトの配列を繰り返す場合、item
(この例ではuser
)はループの各回で配列内のオブジェクトを参照します。テンプレート内では、ドット記法(例: user.id
, user.name
)を使ってそのオブジェクトのプロパティにアクセスできます。
4.3 複雑なオブジェクト構造を持つ配列
配列の要素となるオブジェクトは、さらにネストされたオブジェクトや配列を含むことができます。v-for
の中で、ネストされたデータ構造にアクセスしたり、さらにv-for
をネストして使用したりすることも可能です。
“`html
書籍リスト
-
{{ book.title }} by {{ book.author }}
ジャンル: {{ book.genre }}
レビュー
- 評価: {{ review.rating }}星, コメント: “{{ review.comment }}”
“`
この例では、各book
オブジェクトの中にreviews
という配列が含まれています。外側のv-for="book in books"
で書籍を繰り返し、その内側で別のv-for="review in book.reviews"
を使って各書籍のレビュー配列を繰り返しています。このように、v-for
をネストすることで、複雑な階層構造を持つデータを表現できます。
4.4 範囲の繰り返し (v-for="n in number"
)
特定の回数だけ要素を繰り返したい場合は、整数を指定することもできます。
“`html
1から10までのリスト
- アイテム {{ n }}
“`
生成されるHTML:
“`html
- アイテム 1
- アイテム 2
- アイテム 3
- アイテム 4
- アイテム 5
- アイテム 6
- アイテム 7
- アイテム 8
- アイテム 9
- アイテム 10
“`
v-for="n in 10"
のように整数N
を指定すると、v-for
はその要素をN
回繰り返します。繰り返しの中で取得できるn
は、1
からN
までの数値になります(配列のインデックスと異なり、1から始まる点に注意してください)。これは、例えばページネーションのリンクを生成する場合や、特定の回数だけダミー要素を表示したい場合などに便利です。
5. オブジェクトのプロパティの繰り返し
v-for
は配列だけでなく、オブジェクトのプロパティを繰り返すこともできます。これは、オブジェクトのすべてのプロパティ名とその値、さらにはインデックスが必要な場合に役立ちます。
オブジェクトを繰り返す場合の構文は以下の通りです。
“`html
ユーザー情報
- {{ index + 1 }}. {{ key }}: {{ value }}
“`
生成されるHTML:
“`html
- 1. name: Alice
- 2. age: 30
- 3. city: Tokyo
“`
構文は (value, key, index) in object
となります。括弧 ()
で囲んで複数の引数を指定できます。
value
: 現在のプロパティの値。key
: 現在のプロパティ名(キー)。index
: 現在のプロパティのインデックス(オブジェクトの列挙順。JavaScriptの仕様上、オブジェクトのプロパティの列挙順は完全に保証されるわけではありませんが、通常は定義された順になります)。
これらの引数は、必要なものだけを取得できます。
(value) in object
: 値のみを取得(value, key) in object
: 値とキーを取得(value, key, index) in object
: 値、キー、インデックスを取得
オブジェクトの繰り返しは、例えば設定情報を表示したり、オブジェクトのすべてのプロパティをデバッグ目的で一覧表示したりする場合などに使用できます。ただし、配列の繰り返しに比べると使用頻度は低いかもしれません。通常、データ構造が固定されている場合は、オブジェクトのプロパティには直接アクセスします (user.name
)。オブジェクトの繰り返しは、プロパティ名が動的に変化する、あるいはプロパティ構造が事前に不明な場合に検討する価値があります。
6. key
属性の重要性
v-for
を使ってリスト要素をレンダリングする際に、Vueに推奨されている、あるいは場合によっては必須となるのがkey
属性の使用です。
“`html
- {{ item.name }}
“`
この例では、<li>
要素に:key="item.id"
という属性が追加されています。ここで:key
はv-bind:key
の省略記法です。key
属性には、リスト内の各要素を一意に識別できるような値を指定する必要があります。よく使われるのは、データのID(データベースのプライマリキーなど)です。
なぜkey
属性が重要なのか?
Vueは、リストを描画する際にデフォルトで「インプレースパッチ(就中パッチ)」という最適化戦略を採用します。これは、同じタイプの要素が並んでいる場合、データの変更があったときに、既存のDOM要素をできるだけ再利用して、変更された部分だけを更新するという仕組みです。
例えば、リストの真ん中に新しい要素が挿入された場合を考えます。key
属性がない場合、Vueは単にリストの各要素を順番に比較し、データ配列の変更に合わせて既存のDOM要素の内容を更新しようとします。新しい要素が挿入されると、その位置以降のすべての要素の内容が一つずれてしまうため、Vueはそれらの要素の内容をすべて更新する必要が出てきます。
しかし、key
属性を指定すると、VueはそれぞれのDOM要素とデータのペアをそのkey
値で関連付けて管理します。データ配列が変更されたとき、Vueは新しい配列と古い配列を比較し、それぞれの要素のkey
値を見て、どのデータがどのkey
の要素に対応するかを正確に判断します。
このkey
値を基準にした比較により、Vueは以下の操作を非常に効率的に行えます。
- 要素の移動: 配列内で要素の位置が変わった場合、Vueは既存のDOM要素を破棄せずに、正しい位置に移動させます。
- 要素の挿入: 新しい
key
を持つ要素が配列に追加された場合、その新しい要素に対応するDOM要素だけを作成し、適切な位置に挿入します。 - 要素の削除: 古い配列には存在したが新しい配列には存在しない
key
を持つ要素は、対応するDOM要素が効率的に削除されます。 - 要素の内容更新: 同じ
key
を持つ要素で内容だけが変更された場合、VueはそのDOM要素の内容だけをピンポイントで更新します。
これらの操作が効率的に行われることで、特に大規模なリストや、頻繁に要素の追加・削除・並べ替えが発生するリストにおいて、DOM操作のオーバーヘッドが減り、アプリケーションのパフォーマンスが大幅に向上します。
さらに重要なのは、key
を使うことで、Vueがリスト内の要素のアイデンティティを正確に追跡できる点です。これは、リスト内の要素がフォーム入力の状態やアニメーション状態などのローカルな状態を持っている場合に特に重要になります。key
がないと、データが入れ替わったときにDOM要素が再利用されてしまい、意図しない要素に以前の状態が引き継がれてしまう可能性があります。key
を指定することで、特定のデータに対応するDOM要素は、データがリストから削除されるまで「同じ要素」として扱われ、状態が適切に維持されます。
key
属性には何を指定すべきか?
key
属性には、そのリスト内において一意で安定した値を指定する必要があります。
- 一意性: リスト内のすべての要素は、異なる
key
値を持つ必要があります。 - 安定性: リストが再レンダリングされても、同じデータ要素には常に同じ
key
値が割り当てられる必要があります。
したがって、リストデータの各要素に存在する一意なID(例えば、データベースのレコードID、UUIDなど)をkey
として使用するのが最も推奨される方法です。
“`html
“`
もしデータ自体に一意なIDがない場合は、サーバーサイドで生成して提供するか、クライアントサイドで一意性を確保できる値を生成することを検討してください。例えば、ライブラリを使ってUUIDを生成するなどが考えられます。
key
にインデックスを使う場合の注意点
v-for="(item, index) in items"
のように、インデックスをkey
として使う例もよく見られます。
“`html
“`
これは、リストが静的であり、要素の追加・削除・並べ替えが一切発生しないことが確実な場合には問題ないかもしれません。しかし、リストが動的に変更される可能性がある場合は、インデックスをkey
として使用することは推奨されません。
なぜなら、リストの途中で要素が追加・削除・並べ替えられると、同じデータ要素であってもそのインデックスが変わってしまうからです。インデックスがkey
に使われていると、Vueはインデックスの変更を「別の要素になった」と誤解し、本来再利用できるDOM要素を破棄して再生成してしまう可能性があります。これにより、パフォーマンスの低下を招いたり、前述したローカルな状態の維持に関する問題が発生したりすることがあります。
インデックスをkeyに使うべきではない典型的なケース:
- リストの途中に要素を追加・削除する場合
- リストを並べ替える場合 (
sort
) - リストをフィルタリングして新しいリストを表示する場合 (
filter
)
これらの操作を行う際に、インデックスをkey
にしていると、VueがDOMの再利用を効果的に行えず、非効率なDOM更新が発生する可能性があります。
結論として:
v-for
を使用する際は、必ずkey
属性を指定する習慣をつけましょう。key
属性には、リスト内の要素を一意に識別できる安定したIDを指定するのがベストプラクティスです。- リストが静的で一切変更されないことが確実な場合を除き、インデックスを
key
に使用することは避けましょう。
key
属性を指定しない場合、Vue 2では警告が表示されます。Vue 3では、インデックスをデフォルトのkey
として使用しますが、前述の理由から動的なリストでは明示的に一意なIDをkey
として指定することが強く推奨されています。
7. v-for
と他のディレクティブの組み合わせ
v-for
は、他のVueディレクティブと組み合わせて使用することで、より柔軟で表現力豊かなリスト表示を実現できます。
7.1 v-for
とv-if
の組み合わせ
リストの要素を繰り返し表示する際に、特定の条件を満たす要素だけを表示したい場合があります。このような場合に、v-for
とv-if
を組み合わせて使用します。
同じ要素にv-if
とv-for
を記述する場合
“`html
在庫のある商品
- {{ product.name }}
“`
このコードでは、<li>
要素にv-for
とv-if
の両方が記述されています。
Vue 2とVue 3での挙動の違い:
- Vue 2: 同じ要素に
v-if
とv-for
が記述されている場合、v-for
よりもv-if
の方が優先度が高く評価されます。つまり、v-if
の条件を満たした場合にのみループが実行されます。この例で言えば、products
配列をループする前に、まずproduct.inStock
が真であるかを評価し、真である要素に対してのみループ本体(<li>
要素の生成)を行います。
問題点: これは直感に反する場合があります。プロパティ名がループのスコープ内で使えない可能性があったり、リスト全体をフィルタリングするような場合は非効率になることがあります。 - Vue 3: 同じ要素に
v-if
とv-for
が記述されている場合、v-for
の方が優先度が高く評価されます。つまり、まずv-for
によって各要素に対してループが実行され、そのループの各回でv-if
の条件が評価されます。条件が真であればその要素が描画され、偽であればスキップされます。この例では、products
配列をまずループし、各product
に対してproduct.inStock
を評価し、真なら<li>
を描画します。
利点: これはより直感的で、v-for
のスコープ内でv-if
の条件式を書くことができます。
推奨される方法 (<template>
タグを使用)
同じ要素にv-for
とv-if
を記述するのは、特にVue 2では挙動が分かりにくく、Vue 3でも読みにくい場合があります。一般的には、以下のいずれかの方法でv-if
とv-for
を分離することが推奨されます。
パターン1: 親要素でv-if
、子要素でv-for
リスト自体を条件付きで表示したい場合は、親要素にv-if
を、リスト要素にv-for
を付けます。
“`html
-
{{ product.name }} (在庫: {{ product.inStock ? ‘あり’ : ‘なし’ }})
商品がありません。
“`
これは、リスト全体が存在するかどうかを制御したい場合に適しています。
パターン2: <template>
タグでv-for
、その内側でv-if
リストの中から条件を満たす要素だけをフィルタリングして表示したい場合は、v-for
を<template>
タグに適用し、その内側の要素にv-if
を適用するのが最もクリーンで推奨される方法です。<template>
タグは実際のDOM要素としては描画されませんが、ディレクティブを適用するためのラッパーとして機能します。
“`html
在庫のある商品 (template + v-if)
- {{ product.name }}
在庫のある商品 (computed + v-for) – より推奨
- {{ product.name }}
“`
<template v-for="...">
を使う方法は、Vue 2とVue 3の両方で正しく機能し、意図が明確になります。
ただし、リストをフィルタリングしたりソートしたりして表示する場合は、computed
プロパティを使ってフィルタリング/ソート済みの新しい配列を生成し、そのcomputed
プロパティをv-for
の対象とするのが最も推奨される方法です。上の例の「在庫のある商品 (computed + v-for)」のセクションがこれにあたります。
computed
プロパティを使う利点は以下の通りです。
- 明確さ: フィルタリング/ソートのロジックがテンプレートから分離され、JavaScript部分に集約されます。
- 効率: データの変更がない限り、
computed
プロパティは結果をキャッシュします。フィルタリング/ソート処理が重い場合でも、不要な再計算を防ぐことができます。 - 再利用性: フィルタリング/ソートされたリストを複数の場所で使用したい場合でも、
computed
プロパティを使い回すことができます。
パフォーマンスの観点からも、大規模なリストを扱う場合は、テンプレート内で直接v-if
でフィルタリングするよりも、computed
プロパティで前処理してからv-for
で表示する方が効率的になることが多いです。
7.2 v-for
とv-show
の組み合わせ
v-show
は要素のCSS display
プロパティを切り替えることで表示・非表示を制御します。v-if
とは異なり、要素自体は常にDOMにレンダリングされます。
v-for
とv-show
を組み合わせることも可能ですが、通常は同じ要素に同時に適用されることはありません。v-show
は要素を非表示にするだけなので、リストの中から特定の要素を「描画自体しない」のではなく、「描画するが非表示にする」という場合に利用します。
“`html
表示/非表示可能なアイテム
- {{ item.name }}
“`
この例では、v-show="item.visible"
によって、visible
プロパティがfalse
の要素は非表示になります。すべての要素はDOMに存在するため、頻繁に表示/非表示が切り替わるような場合にv-show
はパフォーマンス上の利点を持つことがあります(v-if
は要素の生成/破棄を行うため)。
ただし、リストの多くの要素が非表示になる可能性がある場合は、v-if
でそもそも描画しないか、computed
プロパティでフィルタリングする方が、DOM要素の数を減らせるためパフォーマンスが良いことが多いです。v-show
は、少数の要素を頻繁に切り替える場合に検討すると良いでしょう。
7.3 v-for
とコンポーネントの組み合わせ
リストの各アイテムが複雑な構造を持つ場合や、再利用可能なUI部品である場合は、リストアイテムをVueコンポーネントとして定義し、v-for
を使ってそのコンポーネントを繰り返しレンダリングするのが非常に効果的です。
“`html
商品リスト (コンポーネント)
“`
``javascript
// product-item.js (コンポーネントの定義)
const ProductItem = {
template:
`,
props: {
product: {
type: Object,
required: true
}
},
methods: {
addToCart() {
// 親コンポーネントにイベントを送信
this.$emit(‘add-to-cart’, this.product);
}
}
};
// グローバル登録 (またはローカル登録)
// app.component(‘product-item’, ProductItem); // index.html側で行う場合
“`
この例では、product-item
というコンポーネントを作成し、v-for
を使って各商品データをプロパティ(product
)としてそのコンポーネントに渡しています。
コンポーネントとv-for
を使う利点:
- コードの構造化と再利用性: 各リストアイテムのレンダリングロジックやインタラクションロジックがコンポーネント内にカプセル化されるため、コードが整理され、同じコンポーネントをアプリケーションの他の場所でも再利用しやすくなります。
- 管理の容易さ: 各アイテムが独自のロジックや状態(例えば、お気に入り登録の状態、数量入力など)を持つ場合、それをコンポーネント内部で管理できます。
- 可読性: 複雑なリストアイテムのマークアップがコンポーネントに分離されるため、親コンポーネントのテンプレートがシンプルになり、全体的なコードの可読性が向上します。
- パフォーマンス (部分的な更新): Vueはコンポーネントを単位として更新を最適化します。リスト内の特定のアイテムのデータだけが変更された場合、Vueはそのアイテムに対応するコンポーネントだけを効率的に更新できます。
- 必須の
key
属性: コンポーネントをv-for
で繰り返しレンダリングする場合、key
属性は必須です。Vueはコンポーネントインスタンスを正確に追跡するためにkey
を利用します。:key="product.id"
のように、必ず一意で安定した値を指定してください。
リストアイテムが単純なテキスト表示だけであれば<li>
タグに直接v-for
を使えば十分ですが、アイテムがボタンやフォーム要素を含んだり、自身の状態を持ったりする場合は、コンポーネント化を強く検討すべきです。
8. 配列変異メソッドと非変異メソッド:Vueの変更検知の仕組み
Vueは、データが変更されたときにリアクティブにUIを更新します。しかし、JavaScriptの配列やオブジェクトのすべての変更方法をVueが自動的に検知できるわけではありません。v-for
で表示しているリスト(配列やオブジェクト)のデータをJavaScriptで操作する場合、Vueが変更を検知できる方法とできない方法があることを理解しておくことが重要です。
8.1 配列の変更検知
Vueは、以下の配列の「変異メソッド」に対して、リアクティブな更新を提供します。これらのメソッドは元の配列を変更(変異)させます。
push()
: 末尾に要素を追加pop()
: 末尾の要素を削除shift()
: 先頭の要素を削除unshift()
: 先頭に要素を追加splice()
: 要素の追加・削除・置換sort()
: 要素をソートreverse()
: 要素の並び順を反転
これらのメソッドを使うと、Vueは変更を検知し、v-for
でレンダリングされているリストのUIを自動的に更新します。
“`html
- {{ item }}
“`
Vueが検知できない配列の変更方法:
以下の2つの方法では、Vueは配列の変更を自動的に検知できません(少なくともVue 2のリアクティブシステムでは)。
-
インデックスによる直接設定:
this.items[index] = newValue
例:this.items[0] = '最初のアイテムを置き換え'
-
配列の
length
プロパティの直接変更:
this.items.length = newLength
例:this.items.length = 1; // 配列を最初の1つだけにする
“`html
- {{ item }}
“`
(※注意: Vue 3ではインデックスによる変更はある程度検知されるようになりましたが、公式ドキュメントでは推奨されていません。)
これらの変更をリアクティブにするためには、以下のいずれかの方法を使います。
-
Vue.set
(Vue 2) /set
関数 (Vue 3):
Vue.set(target, propertyName/index, newValue)
(Vue 2)
import { reactive, set } from 'vue'; set(target, propertyName/index, newValue)
(Vue 3 Composition API)
this.$set(target, propertyName/index, newValue)
(Vue 2 Instance method)インデックスによる設定の代替:
Vue.set(this.items, 0, 'Z');
(Vue 2)
this.$set(this.items, 0, 'Z');
(Vue 2) -
splice
を使う:
配列の途中の要素を置換するにはsplice
を使います。
this.items.splice(index, 1, newValue);
インデックスによる設定の代替:
this.items.splice(0, 1, 'Z');
// 0番目の要素を1つ削除し、代わりに’Z’を挿入 -
非変異メソッドで新しい配列を作成し、元の配列と置き換える:
filter
,concat
,slice
などの非変異メソッドは、元の配列を変更せずに新しい配列を返します。この新しい配列を元の配列プロパティに代入することで、Vueはプロパティの変更を検知し、UIを更新します。インデックスによる設定の代替 (スプレッド構文などを使う方法):
this.items = [...this.items.slice(0, 0), 'Z', ...this.items.slice(1)];
// 0番目を’Z’に置き換え (少し複雑)
よりシンプルに:this.items = this.items.map((item, i) => i === 0 ? 'Z' : item);
length
変更の代替:
this.items = this.items.slice(0, 1);
// 最初の1つだけを要素とする新しい配列を作成要素のフィルタリング (これは
computed
プロパティで新しい配列を作成するのが最も一般的で推奨される方法ですが、メソッド内で行う例):
this.items = this.items.filter(item => item !== 'B');
// ‘B’を除く新しい配列を作成
“`html
配列操作 (検知可能な方法)
- {{ item }}
“`
リストデータを操作する際は、Vueが変更を正しく検知できるように、変異メソッドを使用するか、非変異メソッドで新しい配列を作成して置き換えるか、あるいはVue.set
($set
/set
)を使用することを忘れないでください。
8.2 オブジェクトの変更検知
配列と同様に、Vueはオブジェクトの特定の変更を自動的に検知できません(特にVue 2のリアクティブシステム)。
Vueが検知できないオブジェクトの変更方法:
-
新しいプロパティの追加:
this.user.newProperty = newValue
例:this.user.job = 'Engineer'
-
既存のプロパティの削除:
delete this.user.age
“`html
オブジェクト操作 (検知されないVue 2)
{{ user.name }} ({{ user.age }}歳) – 職業: {{ user.job }}
“`
(※注意: Vue 3では新しいプロパティの追加も検知されるようになりましたが、推奨されない方法です。)
これらの変更をリアクティブにするためには、以下のいずれかの方法を使います。
-
Vue.set
(Vue 2) /set
関数 (Vue 3):
新しいプロパティの追加の代替:
Vue.set(this.user, 'job', 'Engineer');
(Vue 2)
this.$set(this.user, 'job', 'Engineer');
(Vue 2)
import { reactive, set } from 'vue'; set(this.user, 'job', 'Engineer');
(Vue 3 Composition API) -
新しいオブジェクトを作成し、元のオブジェクトプロパティと置き換える:
既存のプロパティの変更や、複数のプロパティを一度に変更・追加・削除する場合に有効です。スプレッド構文 (...
) を使うのが一般的です。プロパティの追加の代替:
this.user = { ...this.user, job: 'Engineer' };
プロパティの削除の代替:
const newUser = { ...this.user }; delete newUser.age; this.user = newUser;
または、より簡潔にlodashのようなライブラリを使う。
“`html
オブジェクト操作 (検知可能な方法)
{{ user.name }} ({{ user.age }}歳) – 職業: {{ user.job }}
“`
v-for
でオブジェクトのプロパティをリスト表示している場合だけでなく、リアクティブデータとしてのオブジェクトを操作する一般的なルールとして、これらの変更検知の仕組みを理解しておくことが重要です。特に、初期データに含まれていないプロパティを後から追加する場合は注意が必要です。
9. v-for
を使った実践的な例
いくつかの一般的なユースケースでv-for
がどのように使われるかを見てみましょう。
9.1 TODOリストの表示
最も基本的な例の一つです。TODOアイテムの配列を表示します。
“`html
TODOリスト
-
{{ todo }}
“`
この例では、todos
配列をv-for
で繰り返し表示し、新しいTODOを追加する機能と、特定のTODOを削除する機能を実装しています。配列操作にはpush
とsplice
という変異メソッドを使用しているため、Vueが変更を正しく検知し、UIが自動的に更新されます。key
属性には、簡易的にインデックスを使用していますが、もしTODOに一意なIDがある場合はそちらを使うのがより良いです。
9.2 ショッピングカートアイテムの表示
オブジェクトの配列を使ったより複雑な例です。各アイテムは商品情報と数量を持ちます。
“`html
ショッピングカート
-
{{ item.name }} – {{ item.price }}円 x {{ item.quantity }} = {{ item.price * item.quantity }}円
合計金額: {{ totalAmount }}円
“`
この例では、cartItems
というオブジェクトの配列をv-for
で表示しています。各アイテムにはid
, name
, price
, quantity
が含まれています。数量の増減は各アイテムオブジェクトのquantity
プロパティを直接変更しています(これはVueによって検知されます)。アイテムの削除はfilter
を使って新しい配列を作成し、元の配列を置き換えています。computed
プロパティを使って合計金額を計算し、cartItems
が変更されるたびに自動的に更新されるようにしています。key
属性にはアイテムの一意なid
を使用しており、リストの操作(追加・削除・数量変更)が効率的に行われます。
9.3 データテーブルの表示
テーブルの行や列を繰り返し表示する場合にもv-for
は欠かせません。
“`html
ユーザー一覧
ID | 名前 | 年齢 |
---|---|---|
{{ user.id }} | {{ user.name }} | {{ user.age }} |
“`
この例では、<tbody>
タグにv-for
を適用して、users
配列の各オブジェクトに対して<tr>
(テーブルの行)を生成しています。各<tr>
の中で、さらに<td>
(テーブルのセル)にユーザーのプロパティを表示しています。テーブル構造でリストを表示する場合も、基本的に配列の繰り返しと同じ考え方でv-for
を使用します。key
属性を<tr>
要素に適用している点に注目してください。
10. パフォーマンスに関する考慮事項
v-for
は非常に効率的にリストをレンダリングしますが、大規模なリストを扱う場合や、リストアイテムが複雑な場合は、パフォーマンスに関する考慮が必要になることがあります。
10.1 key
属性の重要性 (再強調)
前述の通り、key
属性はリストレンダリングのパフォーマンスにおいて最も重要な要素です。一意で安定したkey
を使用することで、VueはDOMの再利用を最大限に行い、不要な要素の破棄や再生成を避けることができます。特に要素数が多いリストや、頻繁にリスト構造が変わる(追加・削除・並べ替え)リストでは、key
属性の有無がパフォーマンスに大きく影響します。
10.2 複雑なリストアイテムとコンポーネント化
リストの各アイテムのテンプレートが非常に複雑だったり、多くのDOM要素を含んだりする場合、すべてのアイテムを一度にレンダリングすることは、ブラウザのパフォーマンスに影響を与える可能性があります。このような場合、リストアイテムを独立したコンポーネントに切り出すことが推奨されます。コンポーネント化により、Vueは各アイテムの更新を個別に最適化しやすくなります。また、後述する仮想スクロールなどの技術とも組み合わせやすくなります。
10.3 大規模なリストと仮想スクロール
数千、数万といった非常に多くのアイテムを含むリストを扱う場合、すべてのアイテムを一度にDOMに描画することは、ブラウザのメモリ使用量やレンダリング時間に大きな負荷をかける可能性があります。このようなケースでは、「仮想スクロール(Virtual Scrolling)」または「ウィンドウ処理(Windowing)」と呼ばれるテクニックが効果的です。
仮想スクロールは、画面に表示されている(あるいは表示されそうになっている)アイテムだけをDOMに描画し、スクロールに応じて動的にDOM要素を再利用・更新する仕組みです。これにより、DOM要素の総数を大幅に削減し、パフォーマンスを向上させることができます。
Vue.jsで仮想スクロールを実現するためのライブラリがいくつか存在します。代表的なものには、vue-virtual-scroller
やvue-window
などがあります。これらのライブラリは通常、v-for
の代わりに独自のコンポーネントを提供しており、そのコンポーネントにリストデータを渡すことで仮想スクロールが実現されます。
“`html
<RecycleScroller
class=”scroller”
:items=”list”
:item-size=”50″
key-field=”id”
<template v-slot="{ item }"> <div class="user"> {{ item.name }} </div> </template>
``
vue-virtual-scroller`の基本的な使用イメージであり、実際のコードはライブラリのドキュメントを参照してください。)
(これは
リストが非常に大規模になる可能性がある場合は、最初からこれらの仮想スクロールライブラリの導入を検討する価値があります。
10.4 フィルタリングやソートされたリストの表示 (computed
)
前述の通り、リストをフィルタリングしたりソートしたりして表示する場合は、computed
プロパティを使うのがベストプラクティスです。
javascript
// computed プロパティの例
computed: {
// 在庫があり、かつ検索キーワードに一致する商品だけをフィルタリング
filteredProducts() {
return this.products.filter(product => {
const matchesSearch = product.name.includes(this.searchQuery);
const hasStock = product.inStock;
return matchesSearch && hasStock;
});
},
// filteredProducts を名前でソート
sortedFilteredProducts() {
// slice() で元の配列を変更しないようにコピーを作成
return this.filteredProducts.slice().sort((a, b) => a.name.localeCompare(b.name));
}
}
そして、v-for
ではこのcomputed
プロパティを参照します。
“`html
“`
computed
プロパティは依存するデータが変更された場合にのみ再計算されるため、不要なフィルタリングやソート処理を防ぎ、効率的に処理を実行できます。また、処理ロジックをテンプレートから分離できるため、コードの見通しも良くなります。
11. 応用的なトピック
11.1 v-for
と<template>
タグ
セクション7.1でも少し触れましたが、<template>
タグは実際のDOM要素をレンダリングしないラッパーとして機能します。これは、複数の要素に対してグループとしてv-for
を適用したいが、それらを囲む余分なラッパー要素(例えば<div>
など)をDOMに生成したくない場合に便利です。
“`html
商品の詳細とレビュー
-
{{ product.name }}
{{ product.description }}
-
- 評価: {{ review.rating }}, コメント: {{ review.comment }}
“`
この例では、各商品に対して、その商品自体の情報(<li>
)と、そのレビューリスト(別の<li>
と内側の<ul><li>
)をセットで表示しています。もし<template>
タグを使わずにこれらを囲む<div>
などを使った場合、不必要なDOM要素が生成されてしまいます。<template>
タグは、これらを論理的にグループ化しつつ、余分なDOMを生成しないために使用されます。
<template>
タグは、v-if
やv-show
、そしてv-for
といったディレクティブと一緒に使うことで、より柔軟なテンプレート構造を構築できます。
11.2 コンポーネントにおけるv-for
とis
属性
リストアイテムが動的に異なる種類のコンポーネントになる場合(例えば、ニュースフィードで記事、広告、イベント告知など異なる種類のコンテンツを表示する場合)、v-for
と動的コンポーネント(<component is="...">
)を組み合わせることができます。
“`html
フィード
“`
この例では、feedItems
配列の各オブジェクトはtype
プロパティを持っています。v-for
でこの配列を繰り返し、<component :is="item.type + '-card'"
とすることで、item.type
の値に応じて動的にレンダリングするコンポーネントを切り替えています。各アイテムのデータはdata
プロパティとして渡しています。このパターンは、リスト内のアイテムの種類が複数あり、それぞれで異なる表示ロジックやテンプレートが必要な場合に非常に強力です。
12. トラブルシューティングとよくある落とし穴
v-for
を使用する際によく遭遇する問題とその解決策を紹介します。
-
key
に関する警告/エラー:
[Vue warn]: A v-for directive cannot be used on a component root element unless it has an explicit key. See https://vuejs.org/guide/essentials/list.html#with-a-component
[Vue warn]: Elements in iteration expected to have 'v-bind:key' directives.
(Vue 2)
これは、リストを繰り返している要素やコンポーネントにkey
属性が指定されていない場合に表示される警告です。前述の通り、パフォーマンスと正確なDOM更新のためにkey
属性はほぼ必須です。一意で安定したIDをkey
として指定してください。インデックスをkey
に使う場合も警告は消えますが、動的なリストでは問題が発生する可能性があることを忘れないでください。 -
配列やオブジェクトの変更がUIに反映されない:
これは、Vueが変更を検知できない方法で配列やオブジェクトを操作している場合に発生します(インデックスによる直接設定、length
の変更、新しいプロパティの追加、プロパティの削除など)。セクション8で説明したように、push
,splice
,filter
などの変異/非変異メソッドを使用するか、Vue.set
($set
/set
)または新しいオブジェクトの置き換えで対応してください。 -
v-for
とv-if
を同じ要素に使用する場合の挙動:
特にVue 2ではv-if
が優先されるため、意図しない挙動になることがあります。セクション7.1で推奨されているように、<template>
タグを使って分離するか、computed
プロパティでリストを前処理してからv-for
を使用することで、この問題を回避し、コードの可読性も向上させることができます。 -
ネストされた
v-for
でのスコープの問題:
ネストされたv-for
では、内側のループは外側のループの変数を参照できます。html
<div v-for="item in items">
<div v-for="subItem in item.subItems">
{{ item.name }} - {{ subItem.value }} <!-- 内側で外側のitemにアクセス可能 -->
</div>
</div>
しかし、内側のループで定義した変数(例:subItem
)は、外側のループのスコープでは利用できません。スコープを意識して変数名を使用してください。 -
リスト内の要素に対するイベントハンドラ:
v-for
で生成された各要素にイベントハンドラ(例:@click
)を設定する場合、そのハンドラ関数はループ内で定義された変数(item
,index
など)にアクセスできます。これは意図した挙動ですが、もしハンドラ関数が複雑な処理を行う場合は、親コンポーネントのメソッド内で処理を定義し、必要な引数(item
,index
など)を渡すようにしましょう。リストアイテムをコンポーネント化している場合は、子コンポーネントから親コンポーネントにイベントを$emit
で通知し、親で処理を行うのが一般的です。
これらの落とし穴を理解しておけば、v-for
を使った開発がスムーズに進むでしょう。
13. まとめ
この記事では、Vue.jsのv-for
ディレクティブについて、基本的な使い方から応用的なテクニック、そして知っておくべき重要な注意点まで、非常に詳細に解説しました。
v-for
は、配列やオブジェクトのデータに基づいてHTML要素を繰り返しレンダリングするためのVueの強力なディレクティブです。これにより、宣言的なリスト表示が可能になり、データの変更にUIが自動的に追従します。- 配列の繰り返しは
v-for="item in items"
またはv-for="(item, index) in items"
で行い、プリミティブ型、オブジェクト、ネストされた構造など、様々な種類の配列に対応できます。範囲指定の繰り返しも可能です。 - オブジェクトのプロパティの繰り返しは
v-for="(value, key, index) in object"
で行い、値、キー、インデックスを取得できます。 key
属性は、リストレンダリングのパフォーマンスと正確性を保証するために非常に重要です。 一意で安定したIDをkey
として使用することを強く推奨します。インデックスをkey
に使うのは、リストが完全に静的な場合に限るべきです。v-for
はv-if
やv-show
と組み合わせて、条件付きで要素を表示・非表示にすることができます。特にv-if
との組み合わせでは、<template>
タグやcomputed
プロパティの使用が推奨されます。- 複雑なリストアイテムや再利用可能なアイテムは、コンポーネントとして抽出し、
v-for
で繰り返しレンダリングするのがベストプラクティスです。この場合、key
属性は必須です。動的コンポーネントと組み合わせることで、リストアイテムの種類を動的に切り替えることも可能です。 - Vueのリアクティブシステムは、配列のインデックスによる直接設定や
length
変更、オブジェクトのプロパティの追加・削除をデフォルトでは検知できません(Vue 2)。これらの変更をリアクティブにするためには、push
,splice
,filter
などの変異/非変異メソッド、Vue.set
($set
/set
)、または新しい配列/オブジェクトによる置き換えが必要です。 - 大規模なリストを扱う場合は、仮想スクロールライブラリや、
computed
プロパティによるデータの事前処理(フィルタリング、ソート)を検討することで、パフォーマンスを向上させることができます。
v-for
はVue.jsを使ったアプリケーション開発において最も頻繁に使用されるディレクティブの一つです。その使い方と背後にある仕組み(特にkey
属性と変更検知)をしっかりと理解することで、効率的でメンテナンスしやすい、そしてパフォーマンスの高いリスト表示を実装できるようになります。
この記事が、あなたのVue.js開発におけるv-for
のマスターに役立てば幸いです。
14. 付録・参考資料
- Vue.js 公式ドキュメント – リストレンダリング: https://v3.ja.vuejs.org/guide/list.html (Vue 3)
- Vue.js 公式ドキュメント – リストレンダリング: https://jp.vuejs.org/v2/guide/list.html (Vue 2)
- Vue.js 公式ドキュメント – リアクティブの探求: https://v3.ja.vuejs.org/guide/reactivity-details.html (Vue 3)
- Vue.js 公式ドキュメント – リアクティブの基礎: https://jp.vuejs.org/v2/guide/reactivity.html (Vue 2)
vue-virtual-scroller
(仮想スクロールライブラリ): https://tangramsoftware.github.io/vue-virtual-scroller/
これらの資料も参考にしながら、実際にコードを書いて試してみることをお勧めします。実践を通じて、v-for
の理解がさらに深まるでしょう。
謝辞: 本記事は、Vue.jsの公式ドキュメントを参考に執筆しました。正確な情報については、常に公式ドキュメントをご確認ください。