Appearance
條件與列表渲染
畫面通常不會永遠長一樣。有些區塊要在特定狀態才出現,有些資料則要用列表呈現。Vue 用 v-if、v-show 處理條件,用 v-for 處理列表。
v-if、v-else-if、v-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-if 和 v-for
如果要顯示未完成的待辦事項,直覺可能會寫成這樣:
html
<!-- 錯誤示範:這段程式碼無法運作 -->
<li v-for="todo in todos" v-if="!todo.done" :key="todo.id">
{{ todo.text }}
</li>這不只是可讀性問題,而是根本跑不起來:當 v-if 和 v-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。下一節就來看看它怎麼運作。
