DAY07 | 跟 Vue.js 認識的30天 - Vue 的列表渲染(`v-for`)

v-for 就是 JavaScript 裡的迴圈,基於一組資料來製作相同元素區塊。
先簡單講一下 JavaScript 裡 for...infor...of 的使用方法及差別。

for...in
主要是為遍歷物件屬性。

1
2
3
4
5
6
const obj = {a:1, b:2, c:3}
for(const prop in obj){
// prop 是物件屬性名
console.log(prop) //a b c
console.log(obj[prop]) // 1 2 3
}

for...of
用於可迭代物件,如字串、陣列、DOM NodeList 等等。

1
2
3
4
5
const arr =  ['a', 'b', 'c']
for(const value of arr){
// value 是陣列值
console.log(value) //a b c
}

MDN - for…in

MDN - for…of

v-for 指令

迭代陣列

僅有值

1
2
<!--item是從items代入的資料(從索引值0開始一筆一筆的代入)-->
<div v-for="item in items">{{ item.type }}</div>

放入索引值

1
2
<!--item是從items代入的資料(從索引值0開始一筆一筆的代入),index是代入資料(item)的索引值-->
<div v-for="(item,index) in items">{{ index }}-{{ item.type }}</div>

整體架構會如下:

1
2
3
4
5
6
7
8
9
10
11
<div id="vm">
<div v-for="(item,index) in items">{{ index }}-{{ item.type }}</div>
</div>
<script>
const vm = new Vue({
el:'#vm',
data:{
items:[{type: 'apple', made: 'USA'},{type: 'oppo', made: 'China'}]
}
})
</script>

對於陣列資料的渲染,更推薦使用 v-for="item of items" ,因為在 JS 中 for...offor...in 更適合用在迭代中,因此使用 v-for="item of items" 會更接近 JS 的語法。

迭代物件

1
2
<!--value是物件屬性值,key是物件屬性名,而index是指該key-value pair 是第幾筆資料-->
<div v-for="(value,key,index) in obj">{{ key }}-{{ value }}</div>

渲染出來的結果跟迭代陣列很像。

v-for 指令來迭代數字

1
<div v-for="num in 10">{{ num }}</div>

須注意 num 的值是從 1 開始。

解決 v-for 元素複用

v-forv-if 有相同的問題, Vue 為了更快速的渲染畫面,在資料改變的狀況下,不會移動已經被渲染過的 DOM ,而是直接更新資料(把已經存在的 DOM 元素資料修改成新的值)。

所以可以透過在 HTML 中使用 v-for 的元素加入 key Attribute(值須為唯一,類似 id ),通知 Vue 這個部分資料如果改變,對於已經存在的元素是需要重新排序或是重用一個元素的。

1
2
3
4
5
<!--有key Attribute-->
<div v-for="(user,index) of users">
<label>{{user.label}}</label>
<input :placeholder="user.placeholder" :key="user.label">
</div>

可以參考 Demo :[DAY07]跟 Vue.js 認識的30天 - Vue 的列表渲染 比較差別。

渲染過濾後陣列結果的資料

當需要一個經過濾後的陣列去渲染時,可以創建 computed 去執行過濾,再透過 v-for 渲染對 computed 執行結果去做渲染。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<!--對oddNums的值做渲染-->
<div v-for="odd in oddNums">{{ odd }}</div>

<script>
const vm = new Vue({
el:'#vm',
data:{
numbers: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
},
computed:{
oddNums(){
// 返回 [1, 3, 5, 7, 9]
return this.numbers.filter(num => num%2 === 1)
}
}
})
</script>

利用 templatev-for 做分組

使用到現在,覺得 template 做分組的最大好處真的是減少 <div> 標籤的出現。

1
2
3
4
<template v-for="item in items">
<h3>{{ item.type }}</h3>
<p>{{ item.made }}</p>
</template>

陣列更新如何是響應

在 Vue 文件中有說明,只有 push()pop()shift()unshift()splice()sort()reverse() 等 Vue 會知道陣列有變動。

所以當使用 array[index] 執行更新時,是沒辦法觸發頁面更新的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
const vm = new Vue({
el:'#vm',
data:{
numbers: [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 ]
},
methods:{
changeNum(){
// 網頁不會更新
this.numbers[2] = 13
this.numbers[10] = 11
// 網頁會更新
// this.numbers.splice(2,1,13)
// this.numbers.push(11)
// this.numbers = [...this.numbers,11]
}
}
})

v-forv-if 不要同時出現在同一個元素上

在 Vue 執行的過程中, v-for 的層級較高,所以會在執行完 v-for 才去執行 v-if 的判斷,也因此會造成 v-for 跑完後,才去一個一個執行判斷,之後決定不執行,這是非常多此一舉的。
如果真的需要 v-forv-if 一起使用的話,建議可以在 v-for 外層(可以是 template )元素上加上 v-if ,先跑完判斷,再決定是否要迭代。

1
2
3
4
5
6
7
8
9
10
11
12
13
<!--當show是truthy時,下面迭代才會執行-->
<template v-if="show">
<div v-for="item in items">
<h3>{{ item.type }}</h3>
<p>{{ item.made }}</p>
</div>
</template>
<!--不建議使用下面的做法-->
<!--會先跑迭代,才執行判斷-->
<div v-for="item in items" v-if="show">
<h3>{{ item.type }}</h3>
<p>{{ item.made }}</p>
</div>

Demo:

See the Pen [DAY07]跟 Vue.js 認識的30天 - Vue 的列表渲染 by Celeste6666 (@celeste6666) on CodePen.

參考資料:
Vue.js - v-for
Yes