404 Not Found

404 Not Found


nginx

Vue 3 の条件付きレンダリングとリストレンダリング:v-if と v-for の詳細な比較

条件付きレンダリング(v-if / v-show)とリストレンダリング(v-for)は、Vueテンプレートで最もよく使われる2つのディレクティブです。前者は要素を表示するかどうかを制御し、後者は表示される要素の数を制御します。これらを組み合わせることで、UIのシナリオの90%に対応できます。

v-ifv-forの5つの主な違いを理解し、:keyの正しい使い方を把握することは、高性能なVueアプリケーションを構築するために不可欠です。

1. 学習内容

条件付きレンダリング


2. コードの悪夢:ダッシュボードにおける「ネストされたif文」

(1) 課題:5つ入れ子になった v-if タグ――デバッグの悪夢

アリスのダッシュボードのコードが手に負えなくなってしまった:

VUE
<!-- ❌ The "Flip" Version:v-if Nested Hell -->
<template>
 <div v-if="user">
 <div v-if="user.isAdmin">
 <div v-if="orders.length > 0">
 <div v-if="!isLoading">
 <div v-if="!hasError">
 <!-- Content is finally displayed -->
 <p>Orders: {{ orders.length }}</p>
 </div>
 <div v-else>Error: {{ errorMsg }}</div>
 </div>
 <div v-else>Loading...</div>
 </div>
 <div v-else>No orders</div>
 </div>
 <div v-else>Not admin</div>
 </div>
 <div v-else>Please login</div>
</template>

チャーリーによるコードレビュー:

「アリス、これじゃ読めないよ。v-ifが5段階もネストされてる。もし条件をあと2つ追加したらどうなる?もっとすっきりしたパターンが必要だ。」

(2) Vue での解決策:v-if によるフラット化 + 計算プロパティ

VUE
<template>
 <div v-if="!user">Please login</div>
 
 <div v-else-if="!user.isAdmin">Not admin</div>
 
 <div v-else-if="isLoading">Loading...</div>
 
 <div v-else-if="hasError">Error: {{ errorMsg }}</div>
 
 <div v-else-if="orders.length === 0">No orders</div>
 
 <div v-else>
 <p>Orders: {{ orders.length }}</p>
 <OrderList :orders="orders" />
 </div>
</template>

<script setup>
import { computed } from 'vue'

const props = defineProps(['user', 'orders', 'isLoading', 'hasError', 'errorMsg'])

// Flatten 5 conditions (v-else-if chain)
</script>

(3) 収益


3. v-if / v-else-if / v-else による条件付きレンダリング

(1) 基本的な構文

VUE
<template>
 <!-- Single condition -->
 <p v-if="isVisible">Visible</p>
 
 <!-- Choose one of the two -->
 <p v-if="score >= 60">Passed</p>
 <p v-else>Failed</p>
 
 <!-- Choose one or more -->
 <p v-if="type === 'A'">Type A</p>
 <p v-else-if="type === 'B'">Type B</p>
 <p v-else-if="type === 'C'">Type C</p>
 <p v-else>Unknown</p>
</template>

(2) template要素

VUE
<template>
 <!-- template:Grouping Multiple Elements,Do not render DOM -->
 <template v-if="isAdmin">
 <h1>Admin Dashboard</h1>
 <p>Welcome, {{ user.name }}</p>
 <button>Edit</button>
 </template>
 
 <!-- Does not render template element (Savings 1 DOM layer) -->
</template>

(3) 5つの主なユースケース

シナリオ v-if の使用
ログイン中/未ログイン
読み込み中.../読み込み完了
データなし/データあり
エラー状態
表示/非表示を頻繁に切り替える ❌ (v-show を使用)

4. v-show と v-if の 5 つの主な違い

(1) 主な相違点

VUE
<template>
 <!-- v-if: When conditions are false, Elements removed from DOM -->
 <p v-if="show">v-if</p>
 <!-- show=false: The element does not exist in DOM -->
 
 <!-- v-show: When conditions are false, display: none -->
 <p v-show="show">v-show</p>
 <!-- show=false: <p style="display: none">v-show</p> -->
</template>

(2) 5つの主な比較点

属性 v-if v-show
DOMの操作 条件がfalseのときは要素を削除 常にDOMに残り、displayプロパティのみが切り替わる
スイッチング性能 低い(その都度破棄・再構築) 高い(CSSのみ)
初期レンダリング 条件がfalseの場合はレンダリングしない 常にレンダリングする(非表示の場合でも)
トランジションとの互換性 ✅ 完全対応(イン/アウトアニメーション) ❌ 未対応
利用シーン たまに切り替える場合 頻繁に切り替える場合

(3) 選定に関する推奨事項

JS
// Switch Occasionally(If you are logged in/Not logged in)→ v-if
<div v-if="isLoggedIn">Welcome, {{ user.name }}</div>
<div v-else>Please login</div>

// Frequent switching (e.g. tab switch) → v-show
<button v-show="isActive">Active</button>
<button v-show="!isActive">Inactive</button>

5. v-for を使ったリストのレンダリング

(1) 配列の反復処理

VUE
<template>
 <!-- Basic Iteration -->
 <ul>
 <li v-for="item in items" :key="item.id">
 {{ item.name }}
 </li>
 </ul>
 
 <!-- Indexed -->
 <ul>
 <li v-for="(item, index) in items" :key="item.id">
 {{ index + 1 }}. {{ item.name }}
 </li>
 </ul>
</template>

<script setup>
import { ref } from 'vue'
const items = ref([
 { id: 1, name: 'Apple' },
 { id: 2, name: 'Banana' },
 { id: 3, name: 'Cherry' }
])
</script>

(2) オブジェクトの反復処理

VUE
<template>
 <ul>
 <li v-for="(value, key, index) in user" :key="key">
 {{ index + 1 }}. {{ key }}: {{ value }}
 </li>
 </ul>
</template>

<script setup>
import { reactive } from 'vue'
const user = reactive({
 name: 'Alice',
 age: 25,
 email: 'alice@example.com'
})
// Output:1. name: Alice 2. age: 25 3. email: alice@example.com
</script>

(3) 数値の反復処理

VUE
<template>
 <!-- Rendering 1 to 10 -->
 <span v-for="n in 10" :key="n">{{ n }}</span>
 <!-- Output:12345678910 -->
</template>

(4) 文字列の反復処理

VUE
<template>
 <!-- One character per line span -->
 <span v-for="char in 'Hello'" :key="char">{{ char }}</span>
 <!-- Output:H e l l o -->
</template>

6. :key の目的とベストプラクティス

(1) :key とは何ですか?

:key VueがDOMを効率的に更新できるよう、各v-for項目に一意の識別子を割り当てます。

VUE
<template>
 <!-- ❌ None :key(Vue Use by default index) -->
 <li v-for="item in items">{{ item.name }}</li>
 
 <!-- ✅ Has :key (Use "unique" id) -->
 <li v-for="item in items" :key="item.id">{{ item.name }}</li>
</template>

(2) なぜ :key が必要なのでしょうか?

100%
graph LR
 A[Original List A B C] --> B[New List B C D]
 B --> C[None key:Possible misalignment]
 B --> D[Has key: Update Correctly]
 
 style C fill:#ff6b6b
 style D fill:#42b883

:key: が存在しない場合

:key が存在する場合:

(3) :key のベストプラクティス

VUE
<!-- ✅ Recommendations:The Only One ID -->
<li v-for="user in users" :key="user.id">

<!-- ✅ Second choice:Unique Field(Email、Mobile phone number, etc.)-->
<li v-for="user in users" :key="user.email">

<!-- ❌ Avoid:index(When the data changes, the alignment is off.)-->
<li v-for="(user, index) in users" :key="index">

<!-- ❌ Never, under any circumstances:random()(Every render is different,Forced Rebuild)-->
<li v-for="user in users" :key="Math.random()">

7. リストのフィルタリング/並べ替え/ページネーション

(1) フィルタリングと並べ替え(計算プロパティ)

JS
import { ref, computed } from 'vue'

const items = ref([
 { id: 1, name: 'iPhone', price: 999, category: 'phone' },
 { id: 2, name: 'MacBook', price: 2499, category: 'laptop' },
 { id: 3, name: 'iPad', price: 599, category: 'tablet' }
])

const searchQuery = ref('')
const selectedCategory = ref('all')
const sortBy = ref('price') // 'price' / 'name'

// Filter + Sort(computed Automatic Caching)
const filteredItems = computed(() => {
 let result = items.value
 
 // 1. Filter by search term
 if (searchQuery.value) {
 result = result.filter(item =>
 item.name.toLowerCase().includes(searchQuery.value.toLowerCase())
 )
 }
 
 // 2. Filter by Category
 if (selectedCategory.value !== 'all') {
 result = result.filter(item => item.category === selectedCategory.value)
 }
 
 // 3. Sort
 result = [...result].sort((a, b) => {
 if (sortBy.value === 'price') return a.price - b.price
 if (sortBy.value === 'name') return a.name.localeCompare(b.name)
 return 0
 })
 
 return result
})

(2) ページネーション

JS
const currentPage = ref(1)
const pageSize = ref(10)

// Data after pagination
const paginatedItems = computed(() => {
 const start = (currentPage.value - 1) * pageSize.value
 return filteredItems.value.slice(start, start + pageSize.value)
})

// Total Number of Pages
const totalPages = computed(() =>
 Math.ceil(filteredItems.value.length / pageSize.value)
)

8. 完全な例:動的な商品リスト

(1) ▶ サンプル:. v-if/v-show の5つの活用法

VUE
<template>
 <!-- 1. v-if:Conditional Rendering(Not here DOM) -->
 <p v-if="show">v-if</p>
 
 <!-- 2. v-show:display none(Always there DOM) -->
 <p v-show="show">v-show</p>
 
 <!-- 3. v-else-if:Chained Conditions -->
 <p v-if="type === 'A'">A</p>
 <p v-else-if="type === 'B'">B</p>
 <p v-else>C</p>
 
 <!-- 4. v-if + template:Multi-element -->
 <template v-if="isAdmin">
 <h1>Admin</h1>
 <button>Edit</button>
 </template>
 
 <!-- 5. v-if vs v-show Performance Comparison -->
 <div v-if="occasionally">Switch Occasionally</div>
 <div v-show="frequently">Frequent switching(Not recommended for use in combination with `<transition>`,v-if That's the only way)</div>
</template>
▶ 試してみよう

(2) ▶ サンプル:. v-forの5つの活用法

VUE
<template>
 <!-- 1. Array -->
 <li v-for="item in items" :key="item.id">{{ item.name }}</li>
 
 <!-- 2. Array + Index -->
 <li v-for="(item, i) in items" :key="item.id">
 {{ i + 1 }}. {{ item.name }}
 </li>
 
 <!-- 3. Object -->
 <li v-for="(value, key) in user" :key="key">
 {{ key }}: {{ value }}
 </li>
 
 <!-- 4. Numbers -->
 <span v-for="n in 5" :key="n">{{ n }}</span>
 
 <!-- 5. String -->
 <span v-for="char in 'Hello'" :key="char">{{ char }}</span>
</template>
▶ 試してみよう

(3) ▶ サンプル:. :key の 5 つの使い方の比較

VUE
<template>
 <!-- 1. The Only One ID(Best) -->
 <li v-for="user in users" :key="user.id">{{ user.name }}</li>
 
 <!-- 2. Unique Field -->
 <li v-for="user in users" :key="user.email">{{ user.name }}</li>
 
 <!-- 3. Composite key -->
 <li v-for="post in posts" :key="`${post.userId}-${post.id}`">
 {{ post.title }}
 </li>
 
 <!-- 4. Use index (Second choice) -->
 <li v-for="(item, i) in items" :key="i">{{ item.name }}</li>
 
 <!-- 5. ❌ random()(Never, under any circumstances) -->
 <li v-for="item in items" :key="Math.random()">{{ item.name }}</li>
</template>
▶ 試してみよう

(4) ▶ サンプル:. リストのフィルタリング+ソート+ページネーションの完全な例

VUE
<template>
 <div>
 <!-- Search box -->
 <input v-model="searchQuery" placeholder="Search...">
 
 <!-- Filter by Category -->
 <select v-model="selectedCategory">
 <option value="all">All</option>
 <option value="phone">Phone</option>
 <option value="laptop">Laptop</option>
 </select>
 
 <!-- Sort -->
 <select v-model="sortBy">
 <option value="price">Price</option>
 <option value="name">Name</option>
 </select>
 
 <!-- List -->
 <ul>
 <li v-for="item in paginatedItems" :key="item.id">
 {{ item.name }} - ${{ item.price }}
 </li>
 </ul>
 
 <!-- Pagination -->
 <button :disabled="currentPage === 1" @click="currentPage--">
 Previous
 </button>
 <span>{{ currentPage }} / {{ totalPages }}</span>
 <button :disabled="currentPage === totalPages" @click="currentPage++">
 Next
 </button>
 </div>
</template>

<script setup>
import { ref, computed } from 'vue'

const items = ref([
 { id: 1, name: 'iPhone', price: 999, category: 'phone' },
 { id: 2, name: 'MacBook', price: 2499, category: 'laptop' },
 { id: 3, name: 'iPad', price: 599, category: 'tablet' }
])

const searchQuery = ref('')
const selectedCategory = ref('all')
const sortBy = ref('price')
const currentPage = ref(1)
const pageSize = 10

const filteredItems = computed(() => {
 let result = items.value
 if (searchQuery.value) {
 result = result.filter(i => i.name.includes(searchQuery.value))
 }
 if (selectedCategory.value !== 'all') {
 result = result.filter(i => i.category === selectedCategory.value)
 }
 return [...result].sort((a, b) => {
 if (sortBy.value === 'price') return a.price - b.price
 return a.name.localeCompare(b.name)
 })
})

const paginatedItems = computed(() => {
 const start = (currentPage.value - 1) * pageSize
 return filteredItems.value.slice(start, start + pageSize)
})

const totalPages = computed(() =>
 Math.ceil(filteredItems.value.length / pageSize)
)
</script>
▶ 試してみよう

(5) ▶ サンプル:. よくある5つの間違い

エラー 症状 解決策
なし :key リストの位置ずれ、パフォーマンスの低下 :key="item.id" を追加
v-for と v-if の併用 v-if の優先順位の混乱 template で囲むか、computed を使用する
キーとして「index」を使用する ソート時の不整合 一意のIDを使用する
v-show は <transition> をサポートしていません アニメーション化できません 代わりに v-if を使用してください + <transition>
リストが大きすぎる(100,000件以上) ページがフリーズする 仮想スクロールには vue-virtual-scroller を使用する

(6) ▶ サンプル:. v-if と v-for の優先順位

VUE
<!-- In Vue 3, v-if has higher priority than v-for (Vue 2 is the opposite) -->
<ul>
 <!-- Vue 3: First determine v-if, then v-for -->
 <li v-for="user in users" v-if="user.isActive" :key="user.id">
 {{ user.name }}
 </li>
 <!-- ✅ But Vue officially recommends using template to separate -->
 
 <!-- ❌ Incorrect wording(Not recommended) -->
 <li v-for="user in users" v-if="shouldShowUsers(user)" :key="user.id">
 {{ user.name }}
 </li>
 
 <!-- ✅ Recommendations:computed Filter -->
 <li v-for="user in activeUsers" :key="user.id">
 {{ user.name }}
 </li>
</template>
▶ 試してみよう

❓ よくある質問

Q v-if と v-show のどちらを選べばいいですか?
A たまに切り替える場合は v-if を使い(初期レンダリングの負荷を軽減するため)、頻繁に切り替える場合は v-show を使います(DOM の破壊と再構築を繰り返さないため)。ログイン中/ログアウト中の状態の切り替えには v-if を、タブの切り替えには v-show を使用してください。
Q :keyv-for と併用する必要がありますか?
A パフォーマンスの問題やコンポーネントの状態の不一致を避けるため、:key の使用を強く推奨します。一意の ID (item.id) を最優先とし、次いで index を使用してください。random() は絶対に使用しないでください。
Q v-if と v-for を併用する場合、優先順位はどうなりますか?
A Vue 3 では、v-if が v-for より優先されます(Vue 2 とは逆です)。ただし、公式の推奨としては、これらを併用することは避け、代わりに template を使用して分離するか、computed を使用してフィルタリングすることを推奨しています。
Q template要素の目的は何ですか?
A templateは、DOMにはレンダリングされないVueのロジックラッパー要素です。これは、v-ifv-forが複数の要素を含む場合に、不要なdiv要素の生成を防ぐために使用されます。
Q リストのパフォーマンスが低い場合はどうすればよいですか?
A 3つの最適化方法があります:(1) :keyを追加する;(2) computedを使用してフィルタリング/ソート結果をキャッシュする; (3) リストが大きい場合(10万件以上)、vue-virtual-scroller を使用して仮想スクロールを行い、表示領域のみを描画するようにする。
Q v-for を使ってオブジェクトを反復処理する際、キーと値にアクセスできますか?
A はい。v-for="(value, key, index) in obj" を使用します。パラメータの順序は、値、キー、インデックスです。
Q データがない場合にプロンプトを表示するにはどうすればよいですか?
A v-if と v-else を組み合わせて使用します:<div v-if="items.length">Column </div><div v-else> Data</div>
Q リスト内の選択した項目を強調表示するにはどうすればよいですか?
A 各項目に activeIndex を追加してください:@click="activeIndex = item.id" :class="{ active: activeIndex === item.id }"

📖 まとめ


📝 練習問題

(1) 基礎問題(難易度:⭐)

v-if/v-else を使用して、ログイン状態とログアウト状態を切り替えます:

(2) 上級問題(難易度:⭐⭐)

v-for を使用した To-Do リストの実装:

(3) チャレンジ問題(難易度:⭐⭐⭐)

製品のフィルタリング、ソート、ページネーションのシステムを完全に実装する(5ファイル):

  1. 商品データ:30件。商品名、価格、カテゴリ、作成日時が含まれる。
  2. 検索:名前によるあいまい検索を実行する
  3. カテゴリで絞り込み:5つのカテゴリ(すべて/携帯電話/ノートパソコン/タブレット/ヘッドホン)
  4. 並べ替え:価格(昇順/降順)、名前、最新順
  5. ページネーション:1ページあたり10件表示、ページ番号による移動機能付き
  6. パフォーマンス:フィルタリングおよびソート済みの計算結果をキャッシュする
  7. 大規模リストの最適化:1,000件のテスト項目により、スムーズな動作を保証
Web-Tutorial.com

Web-Tutorial 技術チーム

複数の開発者によって共同維持されているプログラミングチュートリアルプラットフォーム。各チュートリアルは専門分野の開発者が執筆・レビューしています。正確で信頼性の高いコンテンツを目指しています — 問題を見つけた場合はお知らせください。

100%