2024-09-16|閱讀時間 ‧ 約 22 分鐘

EP8 - 列表渲染

List Rendering 看這名字~難道是上一集提到的v-for ?
好像是耶!用迴圈把列表陣列裡頭的內容渲染出來~
框架把html標籤裡頭應用基本程式語法~也許應該連繼承都有?

v-for

v-for 指令用來基於陣列渲染項目列表。其語法為 item in items,其中 items 是數據來源的陣列,item 是當前迭代的元素的別名:

const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="item in items">
{{ item.message }}
</li>

v-for 的範圍內,模板表達式可以訪問所有父範圍的屬性。此外,v-for 也支持一個可選的第二個別名來表示當前項目的索引

const parentMessage = ref('Parent')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>

Try it in the playground

v-for 的變數範圍與 JavaScript 的 forEach 方法類似:

const parentMessage = 'Parent';
const items = [
/* ... */
];

items.forEach((item, index) => {
// 可以訪問外部範圍 `parentMessage`
// 但 `item` 和 `index` 僅在這裡可用
console.log(parentMessage, item.message, index);
});

這段代碼展示了 v-for 的變數範圍如何與 forEach 回調函數的簽名相匹配。你可以像對待函數參數一樣使用解構賦值來簡化 v-for 的項目別名:

<li v-for="{ message } in items">
{{ message }}
</li>

<!-- 使用索引別名 -->
<li v-for="({ message }, index) in items">
{{ message }} {{ index }}
</li>

對於嵌套的 v-for,範圍的工作方式也類似於嵌套函數。每個 v-for 範圍都可以訪問其父範圍:

<li v-for="item in items">
<span v-for="childItem in item.children">
{{ item.message }} {{ childItem }}
</span>
</li>

你也可以使用 of 代替 in,這樣語法更接近 JavaScript 的迭代器語法:

<div v-for="item of items"></div>

v-for with an Object

你也可以使用 v-for 來遍歷物件的屬性。遍歷的順序將基於對物件調用 Object.values() 的結果:

const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
});
<ul>
<li v-for="value in myObject">
{{ value }}
</li>
</ul>

這段代碼會顯示物件 myObject 中所有屬性的值。你也可以提供第二個別名來表示屬性的名稱(也叫做 key):

<li v-for="(value, key) in myObject">
{{ key }}: {{ value }}
</li>

你還可以提供第三個別名來表示索引:

<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>

這樣,v-for 可以遍歷物件的屬性及其值,並且可以選擇性地顯示屬性的名稱和索引。

Try it in the playground

v-for with a Range

v-for 也可以接受一個整數。在這種情況下,它會根據範圍 1...n 重複模板多次。

<span v-for="n in 10">{{ n }}</span>

注意這裡 n 從初始值 1 開始,而不是 0。

v-for on <template>

類似於 v-if,也可在 <template> 標籤上使用 v-for 來渲染多個元素區塊。

<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>

v-for with v-if

注意: 不建議在同一個元素上同時使用 v-ifv-for,因為它們之間存在隱式的優先順序。詳細資訊請參見風格指南。(這個講第二次了www)

v-ifv-for 同時存在於同一個節點上時,v-if 的優先級高於 v-for。這意味著 v-if 條件無法訪問 v-for 的範圍內的變數:

<!--
這會拋出錯誤,因為屬性 "todo" 在實例中未定義。
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>

通過將 v-for 移到 <template> 標籤包裝起來修正這個問題(這樣也更為明確):

<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>

使用key維護狀態 - Maintaining State with key

當 Vue 更新使用 v-for 渲染的元素列表時,預設情況下,它使用 "就地補丁"(in-place patch) 策略。如果資料項目的順序發生了變化,Vue 會在原地補丁每個元素,確保它反映當前應該渲染的內容,而不是移動 DOM 元素以匹配項目的順序。

這種預設模式效率很高,但僅適用於列表渲染輸出不依賴於子組件狀態或臨時 DOM 狀態(例如表單輸入值)的情況。

為了讓 Vue 能夠跟蹤每個節點的身份,從而重用和重新排序現有元素,你需要為每個項目提供一個唯一的 key 屬性:

<div v-for="item in items" :key="item.id">
<!-- 內容 -->
</div>

當使用 <template v-for> 時,key 應該放在 <template> 容器上:

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

注意: 這裡的 key 是一個特殊的屬性,用於綁定 v-bind。它不應與在使用 v-for 遍歷物件時的屬性 key 變數混淆。

建議在使用 v-for 時提供 key 屬性,除非迭代的 DOM 內容很簡單(即不包含組件或有狀態的 DOM 元素),或你有意依賴預設行為以獲得性能提升。

key 綁定應該使用基本值,如字符串和數字。不要使用物件作為 v-forkey。有關 key 屬性的詳細使用,請參見 key API 文檔

v-for with a Component

本節假設你對組件已有了解。如果不熟悉,建議稍後再回來閱讀。

你可以像使用普通元素一樣,直接在組件上使用 v-for(別忘了提供 key):

<MyComponent v-for="item in items" :key="item.id" />

然而,這樣做不會自動將任何數據傳遞給組件,因為組件具有獨立的作用域。為了將迭代數據傳遞給組件,我們還需要使用 props

<MyComponent
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id"
/>

不自動將 item 注入組件的原因是,這樣會使組件與 v-for 的實現方式緊密耦合。明確數據來源使得組件可以在其他情況下重用。

查看這個簡單的待辦事項列表範例,了解如何使用 v-for 渲染一組組件並向每個實例傳遞不同的數據。

陣列變化檢測 - Array Change Detection

變異方法 - Mutation Methods

Vue 能夠檢測到當一個響應式陣列的變異方法被調用時,並觸發必要的更新。這些變異方法包括:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()

替換陣列 - Replacing an Array

變異方法會修改它們所調用的原始陣列。相比之下,還有一些非變異方法,如 filter()concat()slice(),這些方法不會修改原始陣列,而是返回一個新陣列。當使用這些非變異方法時,我們應該用新陣列替換舊陣列:

// `items` 是一個具有陣列值的 ref
items.value = items.value.filter((item) => item.message.match(/Foo/))

你可能會擔心這會導致 Vue 丟棄現有的 DOM 並重新渲染整個列表,但實際上並非如此。Vue 實施了智能的啟發式算法以最大化 DOM 元素的重用,因此用另一個包含重疊對象的陣列替換陣列是一個非常高效的操作。

顯示過濾或排序後的結果 - Displaying Filtered/Sorted Results

有時我們希望顯示過濾或排序後的陣列版本,而不實際修改或重置原始數據。在這種情況下,可以創建一個計算屬性來返回過濾或排序後的陣列。

const numbers = ref([1, 2, 3, 4, 5])

const evenNumbers = computed(() => {
return numbers.value.filter((n) => n % 2 === 0)
})

在這裡,我們有一個 numbers 陣列,並使用 computed 來創建一個新的計算屬性 evenNumbers。這個計算屬性會返回所有偶數。模板可以這樣顯示:

<li v-for="n in evenNumbers">{{ n }}</li>

如果計算屬性不適用(例如在多層嵌套的 v-for 中),可使用方法來過濾或排序數據。

const sets = ref([
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10]
])

function even(numbers) {
return numbers.filter((number) => number % 2 === 0)
}

在這裡,我們有一個 sets 陣列,包含多個數字陣列。我們定義了一個方法 even,用來過濾出偶數。在模板中,可以這樣使用:

<ul v-for="numbers in sets">
<li v-for="n in even(numbers)">{{ n }}</li>
</ul>

使用 reverse()sort() 方法時要小心,因為這些方法會修改原始陣列。為了避免計算屬性中直接修改原始數據,應先創建原始數組的副本,再進行排序或反轉操作。

// 不建議的做法
// return numbers.reverse()

// 推薦的做法 創建原始數組的副本,再進行操作
return [...numbers].reverse()

補充:[...numbers]

...numbers:這是展開運算符(spread operator),用來將 numbers 陣列中的所有元素展開成單獨的元素。例如,如果 numbers[1, 2, 3],那麼 ...numbers 就相當於 1, 2, 3

[...numbers]:這裡用展開運算符將 numbers 陣列中的元素展開,然後包裹在新的陣列中,從而創建一個新的副本,而不改變原始的 numbers 陣列。這意味著你現在有了一個與 numbers 內容相同但獨立於它的新的陣列。

持續學習各種框架特性!
但會有一些例外~什麼東西不能跟什麼東西一起用~
原因是什麼理由是什麼~還需要好好內化吸收一下www
一起來吸收~~~






分享至
成為作者繼續創作的動力吧!
© 2024 vocus All rights reserved.