從零開始的 Vue3 生活- 列表渲染與條件渲染
前言
這篇主要介紹 v-for
、v-if
、v-show
是甚麼,以及如何使用。
v-if
和 v-show
v-if
v-if
這個指令可以把它想成是元素的是 if 語法,當 v-if
的值等於 true
時,才會渲染,否則元素不會出現在網頁上,並且 v-if
也支援 JavaScript 的表達式。Vue 也提供了 v-else
和 v-else-if
,作用是甚麼應該不用我多說了:
<div id="app">
<h1 v-if="who === 'tom'">Hello Tom!</h1>
<h1 v-else-if="who === 'lucy'">Hello Lucy!</h1>
<h1 v-else>Hello Brah!</h1>
</div>
const app = Vue.createApp({
data() {
return {
who: 'Lucy'
}
}
}).mount('#app')
渲染結果為:
<div id="app">
<h1>Hello Lucy!</h1>
</div>
v-show
v-show
用法與 v-if
大致一樣,不同的地方是 v-show
一定會被保留在 DOM,它只是簡單的切換元素的 CSS 屬性 display
,而 v-if
則是如果傳入的值是 false
,那他就不會存在 DOM 裡:
<div id="app">
<!-- 畫面不會顯示,並且不會存在於 DOM -->
<h1 v-if="isShow">Hello Vue3!</h1>
<!-- 畫面不會顯示,但會存在於 DOM -->
<p v-if="isShow">hello Vue2</p>
</div>
const app = Vue.createApp({
data() {
return {
isShow: false
}
}
}).mount('#app')
Tips
v-show
如果不為true
,則對此元素設置display: none
,並且還在 DOM 裡v-if
如果不為true
,則不會出現在 DOM 裡
v-for
v-for
使用陣列
透過 v-for
,我們可以減少重複使用的元素。讓 HTML 結構更明瞭。v-for
要使用 item in items
此種語法。items
就是你傳入的陣列,item
則是被迭代的陣列元素的別名,所以不一定要取名為 item
:
<div id="app">
<ul>
<li v-for="person in persons">
{{ person.name }} - {{ person.age }}
</li>
</ul>
</div>
const app = Vue.createApp({
data() {
return {
persons: [
{ name: 'Tom', age: 22 },
{ name: 'lucy', age: 20}
]
}
}
}).mount('#app')
渲染結果為:
<div id="app">
<ul>
<li>Tom - 22</li>
<li>lucy - 20</li>
</ul>
</div>
v-for
提供了第二個參數 index
參數,就是當前的索引:
<div id="app">
<ul>
<li v-for="(person, index) in persons">
{{ person.name }} - {{ index }} - {{ person.age }}
</li>
</ul>
</div>
渲染結果為:
<div id="app">
<ul>
<li>Tom - 0 - 22</li>
<li>lucy - 1 - 20</li>
</ul>
</div>
Tips
除了使用in
,還可以使用of
語法:<div v-for="item of items"></div>
v-for
使用物件
你也可以傳入一個物件到 v-for
來遍歷物件的所有屬性:
<div id="app">
<div v-for="value in objects">
{{ value }}
</div>
</div>
const app = Vue.createApp({
data() {
return {
objects: {
name: 'tom',
age: 20,
job: 'Frontend Developer'
}
}
}
}).mount('#app')
渲染結果為:
<div id="app">
<div>tom</div>
<div>20</div>
<div>Frontend Developer</div>
</div>
v-for
物件形式可傳入 3 個參數,記得一定要按照順序:
- 屬性值
value
- 屬性名稱
name
- 索引
index
<div id="app">
<div v-for="(value, name, index) in objects">
{{ name }} - {{ value }} - {{ index }}
</div>
</div>
const app = Vue.createApp({
data() {
return {
objects: {
name: 'tom',
age: 20,
job: 'Frontend Developer'
}
}
}
}).mount('#app')
渲染結果為:
<div id="app">
<div>name - tom - 0</div>
<div>age - 20 - 1</div>
<div>job - Frontend Developer - 2</div>
</div>
v-for
使用 key
v-for
更新已渲染的元素列表時,預設使用 “就地複用” 策略,當列表資料修改時,會根據 key
值去判斷某個值是否修改,如果修改,則重新渲染,否則複用之前的元素。而我們在使用時經常會用 v-for
的 index
來當作 key
,但其實非常不推薦這樣做:
假設有個 list 陣列:
<div id="app">
<div v-for="(item, index) in list" :key="index">
</div>
</div>
const app = Vue.createApp({
data() {
return {
list: [
{ id: 1, name: 'test1' },
{ id: 2, name: 'test2' },
{ id: 3, name: 'test3' }
]
}
}
}).mount('#app')
如果在最後加入一個新資料:
const app = Vue.createApp({
data() {
return {
list: [
{ id: 1, name: 'test1' },
{ id: 2, name: 'test2' },
{ id: 3, name: 'test3' },
{ id: 4, name: '這筆資料懂得甚麼叫排隊' },
]
}
}
}).mount('#app')
這時前三筆資料會複用之前的元素,這種時候用 index
作為 key
沒啥問題。
但如果是在中間插入新資料:
const app = Vue.createApp({
data() {
return {
list: [
{ id: 1, name: 'test1' },
{ id: 4, name: '插隊的資料' },
{ id: 2, name: 'test2' },
{ id: 3, name: 'test3' },
]
}
}
}).mount('#app')
此時更新渲染資料會發現:
原先的資料: 插入後的資料:
key: 1, index: 0 name: test1 key: 0, index: 0, name: test1
key: 2, index: 1 name: test2 key: 1, index: 1, name: 這筆資料在插隊
key: 3, index: 2 name: test3 key: 2, index: 2, name: test2
key: 3, index: 3, name: test3
觀察上面會發現除了地一筆資料,其他三筆都被重新渲染了,這就是使用 index
當 key
的弊端,我明明只要修改一項資料,卻要重新渲染其他筆資料。
這裡解決的辦法就是使用 list 裡的不會變動並且唯一的值 id
來當作 key
:
原先的資料: 插入後的資料:
key: 1, id: 1, index: 0 name: test1 key: 1, id: 1, index: 0, name: test1
key: 2, id: 2, index: 1 name: test2 key: 4, id: 4, index: 1, name: 這筆資料在插隊
key: 3, id: 3, index: 2 name: test3 key: 2, id: 2, index: 2, name: test2
key: 3, id: 3, index: 3, name: test3
現在插入後的資料只有一筆資料做了變化,所以只需要新渲染這筆資料就好,其他資料會複用之前的資料。
懶人包
key
請綁定唯一並且不會變動的值,避免因更動一筆資料導致其他資料都需要重新渲染
v-for
不要和 v-if
一起使用
當這兩個指令在同個標籤時, v-if
的優先性會比 v-for
還高,這表示 v-if
無法使用 v-for
裡的變數:
<div id="app">
<!-- Cannot read properties of undefined (reading 'isDone') -->
<div v-for="todo in todos" v-if="!todo.isDone">
{{ todo.name }}
</div>
</div>
const app = Vue.createApp({
data() {
return {
todos: [
{ name: 'learn Vue3', isDone: true },
{ name: 'learn JavaScript', isDone: false },
]
}
}
}).mount('#app')
如果不想多用一個元素包起來,可以使用 <template>
標籤,它不會被渲染到 DOM 上:
<div id="app">
<template v-for="todo in todos">
<div v-if="!todo.isDone">
{{ todo.name }}
</div>
<template/>
</div>