Renderowanie list
v-for
Używamy dyrektywy v-for
, aby renderować listę elementów na podstawie tablicy. Dyrektywa v-for
wymaga specjalnej składni w formie item in items
, gdzie items
to tablica danych źródłowych, a item
to alias elementu tablicy, przez który iterujemy:
js
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
template
<li v-for="item in items">
{{ item.message }}
</li>
W obrębie zakresu v-for
wyrażenia szablonu mają dostęp do wszystkich właściwości z zakresu nadrzędnego. Dodatkowo, v-for
obsługuje opcjonalny drugi alias dla indeksu bieżącego elementu:
js
const parentMessage = ref('Parent')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
template
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
Zmienne w obrębie v-for
są podobne do poniższego kodu JavaScript:
js
const parentMessage = 'Parent'
const items = [
/* ... */
]
items.forEach((item, index) => {
// ma dostęp do zewnętrznego zakresu `parentMessage`
// ale `item` i `index` są dostępne tylko tutaj
console.log(parentMessage, item.message, index)
})
Zauważ, jak wartość v-for
odpowiada sygnaturze funkcji callback w forEach
. Możesz także używać destrukturyzacji w aliasie v-for
podobnie jak w argumentach funkcji:
template
<li v-for="{ message } in items">
{{ message }}
</li>
<!-- with index alias -->
<li v-for="({ message }, index) in items">
{{ message }} {{ index }}
</li>
W przypadku zagnieżdżonych v-for
, zakres również działa podobnie do zagnieżdżonych funkcji. Każdy zakres v-for
ma dostęp do zakresu nadrzędnego:
template
<li v-for="item in items">
<span v-for="childItem in item.children">
{{ item.message }} {{ childItem }}
</span>
</li>
Możesz również używać of
jako separatora zamiast in
, aby składnia była bliższa JavaScriptowi:
template
<div v-for="item of items"></div>
v-for
z obiektem
Możesz również używać v-for
, aby iterować przez właściwości obiektu. Kolejność iteracji będzie oparta na wyniku wywołania Object.values()
na obiekcie:
js
const myObject = reactive({
title: 'How to do lists in Vue',
author: 'Jane Doe',
publishedAt: '2016-04-10'
})
template
<ul>
<li v-for="value in myObject">
{{ value }}
</li>
</ul>
Możesz także podać drugi alias dla nazwy właściwości (tzw. klucza):
template
<li v-for="(value, key) in myObject">
{{ key }}: {{ value }}
</li>
I jeszcze jedno do indeksu:
template
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>
v-for
z zakresem
v-for
może również przyjmować liczbę całkowitą. W takim przypadku powtórzy szablon tyle razy, ile wynosi zakres 1...n.
template
<span v-for="n in 10">{{ n }}</span>
Zauważ, że n
zaczyna się od wartości 1
, a nie od 0
.
v-for
z <template>
Podobnie jak w przypadku v-if
, możesz także użyć tagu <template>
z v-for
, aby wyrenderować blok wielu elementów. Na przykład:
template
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
v-for
z v-if
Kiedy v-if
i v-for
znajdują się na tym samym elemencie, v-if
ma wyższy priorytet niż v-for
. Oznacza to, że warunek v-if
nie będzie miał dostępu do zmiennych z zakresu v-for
:
template
<!--
To spowoduje błąd, ponieważ właściwość "todo"
nie jest zdefiniowana w instancji.
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
Można to naprawić, przenosząc v-for
do opakowującego tagu <template>
(co jest również bardziej jednoznaczne):
template
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
Note
Nie zaleca się używania v-if
i v-for
na tym samym elemencie ze względu na niejawne pierwszeństwo.
Istnieją dwa popularne przypadki, w których może się to wydawać kuszące:
Filtrowanie elementów w liście (np.
v-for="user in users" v-if="user.isActive"
). W tych przypadkach, zamiast filtrować w szablonie, lepiej zastąpićusers
nową właściwością computed, która zwraca przefiltrowaną listę (np.activeUsers
).Unikanie renderowania listy, jeśli ma być ukryta (np.
v-for="user in users" v-if="shouldShowUsers"
). W takich przypadkach przenieśv-if
do elementu kontenerowego (np.ul
,ol
).
Utrzymywanie stanu za pomocą key
Kiedy Vue aktualizuje listę elementów renderowanych za pomocą v-for
, domyślnie stosuje strategię "patchowania w miejscu". Jeśli kolejność elementów danych ulegnie zmianie, Vue nie przenosi elementów DOM, aby dopasować je do nowego porządku, ale zamiast tego dokonuje "patchowania" każdego elementu w miejscu, aby upewnić się, że jest zgodny z tym, co powinno być renderowane pod danym indeksem.
Ten domyślny tryb jest wydajny, ale odpowiedni tylko wtedy, gdy wynik renderowania listy nie zależy od stanu komponentów dziecka lub tymczasowego stanu DOM (np. wartości formularzy).
Aby dać Vue wskazówkę, by mogło śledzić tożsamość każdego węzła i w ten sposób ponownie wykorzystać i zmienić kolejność istniejących elementów, musisz podać unikalny atrybut key
dla każdego elementu:
template
<div v-for="item in items" :key="item.id">
<!-- zawartość -->
</div>
Podczas korzystania z <template v-for>
, key
należy umieścić na kontenerze <template>
:
template
<template v-for="todo in todos" :key="todo.name">
<li>{{ todo.name }}</li>
</template>
Note
Key
jest specjalnym atrybutem, który jest związany z v-bind
. Nie należy go mylić z właściwością zmiennej key
podczas korzystania z v-for
z obiektem. :::
Zaleca się podanie atrybutu key
z v-for
zawsze, gdy to możliwe, chyba że iterowany DOM jest prosty (tzn. nie zawiera komponentów ani stanu DOM), lub jeśli celowo polegasz na domyślnej wydajności.
Atrybut key
oczekuje wartości prymitywnych — tzn. ciągów znaków i liczb. Nie używaj obiektów jako kluczy w v-for
. Szczegółowe informacje o używaniu atrybutu ke
y można znaleźć w dokumentacji API key
.
v-for
z komponentem
Sekcja ta zakłada znajomość komponentów. Jeśli chcesz, możesz ją pominąć i wrócić do niej później.
Możesz bezpośrednio użyć v-for
z komponentem, jak z każdym innym elementem (nie zapomnij o key
):
template
<MyComponent v-for="item in items" :key="item.id" />
Jednak to nie spowoduje automatycznego przekazania danych do komponentu, ponieważ komponenty mają własne izolowane zakresy. Aby przekazać iterowane dane do komponentu, musisz użyć również props:
template
<MyComponent
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id"
/>
Powód, dla którego nie wstrzykuje się automatycznie item
do komponentu, jest taki, że wiązałoby to komponent ściśle z tym, jak działa v-for
. Wyraźne określenie, skąd pochodzą jego dane, sprawia, że komponent nadaje się do ponownego użycia w innych sytuacjach.
Zobacz ten przykład prostej todo list, żeby dowiedzieć się jak renderować listę komponentów za pomocą v-for
, przekazując różne dane do każdej instancji.
Wykrywanie zmian w tablicach
Metody mutujące
Vue jest w stanie wykryć, gdy w tablicy reaktywnej wywoływane są metody mutujące i wywołać niezbędne aktualizacje. Są to metody mutujące:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
Zastępowanie tablicy
Metody mutujące, jak sama nazwa wskazuje, mutują oryginalną tablicę, na której są wywoływane. W porównaniu do nich istnieją również metody niemutujące, takie jak filter()
, concat()
i slice()
, które nie mutują oryginalnej tablicy, ale zawsze zwracają nową tablicę. Pracując z niemutującymi metodami, należy zastąpić starą tablicę nową:
js
// `items` to ref z wartością tablicy
items.value = items.value.filter((item) => item.message.match(/Foo/))
Można pomyśleć, że spowoduje to, że Vue usunie istniejący DOM i ponownie wyrenderuje całą listę – na szczęście tak się nie dzieje. Vue implementuje sprytne heurystyki, aby zmaksymalizować ponowne użycie elementów DOM, więc zastąpienie tablicy inną tablicą zawierającą wspólne obiekty jest bardzo wydajną operacją.
Wyświetlanie przefiltrowanych/posortowanych wyników
Czasami chcemy wyświetlić przefiltrowaną lub posortowaną wersję tablicy, nie mutując ani nie resetując oryginalnych danych. W takim przypadku możesz stworzyć właściwość obliczeniową, która zwróci przefiltrowaną lub posortowaną tablicę.
Na przykład:
js
const numbers = ref([1, 2, 3, 4, 5])
const evenNumbers = computed(() => {
return numbers.value.filter((n) => n % 2 === 0)
})
template
<li v-for="n in evenNumbers">{{ n }}</li>
W sytuacjach, w których obliczone właściwości nie są wykonalne (np. wewnątrz zagnieżdżonych pętli v-for
), możesz użyć metody:
js
const sets = ref([
[1, 2, 3, 4, 5],
[6, 7, 8, 9, 10]
])
function even(numbers) {
return numbers.filter((number) => number % 2 === 0)
}
template
<ul v-for="numbers in sets">
<li v-for="n in even(numbers)">{{ n }}</li>
</ul>
Uważaj na reverse()
i sort()
we właściwości computed! Te dwie metody zmutują oryginalną tablicę, czego należy unikać w getterach computed. Utwórz kopię oryginalnej tablicy przed wywołaniem tych metod:
diff
- return numbers.reverse()
+ return [...numbers].reverse()