Skip to content

條件與列表渲染

重新認識 Vue.js: 008 天絕對看不完的 Vue.js 3.x 指南

畫面通常不會永遠長一樣。有些區塊要在特定狀態才出現,有些資料則要用列表呈現。Vue 用 v-ifv-show 處理條件,用 v-for 處理列表。

v-ifv-else-ifv-else

v-if 會依條件決定是否渲染某段 DOM:

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

const score = ref(82)
</script>

<template>
  <main class="app">
    <p v-if="score >= 90">表現優秀</p>
    <p v-else-if="score >= 60">已經及格</p>
    <p v-else>需要再練習</p>
  </main>
</template>

<style scoped>
.app {
  padding: 2rem;
}
</style>

當條件不成立時,對應的元素不會被建立在 DOM 裡。這很適合用在「有時候根本不需要存在」的內容,例如權限不足提示、尚未載入完成的區塊。

v-show

v-show 也能控制顯示與否,但它不會移除 DOM,而是切換 CSS 的 display

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

const isOpen = ref(false)

function toggle() {
  isOpen.value = !isOpen.value
}
</script>

<template>
  <main class="app">
    <button type="button" @click="toggle">
      切換說明
    </button>

    <p v-show="isOpen" class="panel">
      這段內容會保留在 DOM 裡,只是顯示或隱藏。
    </p>
  </main>
</template>

<style scoped>
.app {
  padding: 2rem;
}

.panel {
  margin-top: 1rem;
}
</style>

簡單判斷方式是:

指令適合情境
v-if條件很少切換,或內容建立成本較高。
v-show內容需要頻繁顯示/隱藏。

v-for 渲染列表

列表渲染會把陣列中的每個項目變成一段模板:

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

const todos = ref([
  { id: 1, text: '建立 Vite 專案', done: true },
  { id: 2, text: '認識模板語法', done: true },
  { id: 3, text: '練習列表渲染', done: false }
])
</script>

<template>
  <main class="app">
    <ul class="todo-list">
      <li v-for="todo in todos" :key="todo.id">
        <label>
          <input v-model="todo.done" type="checkbox">
          <span :class="{ done: todo.done }">{{ todo.text }}</span>
        </label>
      </li>
    </ul>
  </main>
</template>

<style scoped>
.app {
  padding: 2rem;
}

.todo-list {
  padding-left: 1.25rem;
}

.done {
  color: #64748b;
  text-decoration: line-through;
}
</style>

v-for="todo in todos" 會逐一取出 todos 裡的項目。:key="todo.id" 則是告訴 Vue:每個項目的穩定識別值是什麼。

常見錯誤:沒有加 key,或用 index 當 key

v-for 應該搭配穩定且唯一的 key。如果省略 key,或直接用陣列 index,當列表新增、刪除、重新排序時,畫面狀態可能對錯項目。

html
<li v-for="todo in todos">
  {{ todo.text }}
</li>

<li v-for="(todo, index) in todos" :key="index">
  {{ todo.text }}
</li>

<li v-for="todo in todos" :key="todo.id">
  {{ todo.text }}
</li>

能使用資料本身的 id 時,就不要用 index 當 key

取得 index

如果畫面真的需要顯示第幾項,可以在 v-for 取出 index:

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

const chapters = ref([
  { id: 'ch1', title: '現代開發環境' },
  { id: 'ch2', title: 'Vue 基礎心智' },
  { id: 'ch3', title: 'SFC 與 script setup' }
])
</script>

<template>
  <main class="app">
    <ol>
      <li v-for="(chapter, index) in chapters" :key="chapter.id">
        {{ index + 1 }}. {{ chapter.title }}
      </li>
    </ol>
  </main>
</template>

<style scoped>
.app {
  padding: 2rem;
}
</style>

這裡可以使用 index 顯示順序,但 key 仍然使用 chapter.id。這兩件事不要混在一起。

不要在同一個元素混用 v-ifv-for

如果要顯示未完成的待辦事項,直覺可能會寫成這樣:

html
<!-- 錯誤示範:這段程式碼無法運作 -->
<li v-for="todo in todos" v-if="!todo.done" :key="todo.id">
  {{ todo.text }}
</li>

這不只是可讀性問題,而是根本跑不起來:當 v-ifv-for 出現在同一個元素上時,v-if 的優先權比 v-for,會先被執行。這時 v-for 還沒開始跑,todo 這個變數根本不存在,判斷 !todo.done 就會出錯。

正確的做法,是先用 computed 算出要顯示的資料,再讓模板專心渲染:

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

const todos = ref([
  { id: 1, text: '建立 Vite 專案', done: true },
  { id: 2, text: '認識模板語法', done: true },
  { id: 3, text: '練習列表渲染', done: false }
])

const undoneTodos = computed(() =>
  todos.value.filter(todo => !todo.done)
)
</script>

<template>
  <main class="app">
    <p v-if="undoneTodos.length === 0">所有事情都完成了。</p>

    <ul v-else>
      <li v-for="todo in undoneTodos" :key="todo.id">
        {{ todo.text }}
      </li>
    </ul>
  </main>
</template>

<style scoped>
.app {
  padding: 2rem;
}
</style>

到這裡問題已經很清楚:模板負責顯示列表,篩選結果則交給 computed。下一節就來看看它怎麼運作。