Отрисовка списков
Отображение элементов массива через v-for
Используйте директиву v-for
для отрисовки списка элементов на основе массива данных. У директивы v-for
специальный синтаксис: item in items
, где items
— исходный массив, а item
— ссылка на итерируемый элемент массива:
js
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
template
<li v-for="item in items">
{{ item.message }}
</li>
Внутри блока v-for
доступны все свойства из области видимости родителя. Также может быть второй опциональный параметр у v-for
с индексом текущего элемента:
js
const parentMessage = ref('Родитель')
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
template
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
Область видимости переменных в v-for
аналогична следующему коду JavaScript:
js
const parentMessage = 'Родитель'
const items = [
/* ... */
]
items.forEach((item, index) => {
// есть доступ к внешней области видимости и `parentMessage`
// но переменные `item` и `index` доступны только здесь
console.log(parentMessage, item.message, index)
})
Обратите внимание, что значения внутри директивы v-for
совпадают с сигнатурой функции коллбэка forEach
. Вообще-то можно использовать деструктуризацию на переменной текущего элемента, аналогично деструктуризации аргументов функции:
template
<li v-for="{ message } in items">
{{ message }}
</li>
<!-- при использовании переменой для индекса -->
<li v-for="({ message }, index) in items">
{{ message }} {{ index }}
</li>
Для вложенных v-for
область видимости работает аналогично вложенным функциям. Каждая область видимости v-for
имеет доступ к родительским областям видимости:
template
<li v-for="item in items">
<span v-for="childItem in item.children">
{{ item.message }} {{ childItem }}
</span>
</li>
Можно использовать of
в качестве разделителя вместо in
, как в синтаксисе итераторов JavaScript:
template
<div v-for="item of items"></div>
Отображение свойств объекта через v-for
Вы также можете использовать v-for
для итерации по свойствам объекта. Порядок итерации будет основан на результате вызова Object.values()
для объекта:
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>
Можно указать второй аргумент для получения имени свойства (ключа объекта):
template
<li v-for="(value, key) in myObject">
{{ key }}: {{ value }}
</li>
И третий — для индекса:
template
<li v-for="(value, key, index) in myObject">
{{ index }}. {{ key }}: {{ value }}
</li>
v-for
и диапазоны
Можно передавать целое число в v-for
— шаблон будет повторяться указанное число раз.
template
<span v-for="n in 10">{{ n }}</span>
Обратите внимание, что в таком случае значения n
начинаются с 1
, а не с 0
.
v-for
и <template>
Аналогично использованию с v-if
, также можно использовать тег <template>
с директивой v-for
для отрисовки блоков из нескольких элементов. Например:
template
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider" role="presentation"></li>
</template>
</ul>
v-for
и v-if
Примечание
Обратите внимание, не рекомендуется использовать вместе v-if
и v-for
на одном элементе. Подробнее можно прочитать в разделе рекомендаций.
Когда они указаны вместе на одном узле, у v-if
будет больший приоритет, чем у v-for
. И поэтому в условии v-if
не будет доступа к переменным из области видимости v-for
:
template
<!--
Будет выброшена ошибка, так как свойство "todo" не объявлено в экземпляре.
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
Это можно исправить, для ясности переместив v-for
на тег-обёртку <template>
:
template
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
Сохранение состояния с помощью key
При обновлении Vue списка элементов, отрисованного директивой v-for
, по умолчанию используется стратегия обновления «на месте». Если порядок элементов массива или объекта изменился, Vue не станет перемещать элементы DOM, а просто обновит каждый элемент «на месте», чтобы он отображал новые данные по соответствующему индексу.
Режим по умолчанию эффективен, но применим только в случаях, когда результат отрисовки списка не полагается на состояние дочерних компонентов или временное состояние DOM (например, значения в полях форм).
Чтобы подсказать Vue, как определять идентичность каждого элемента, и, таким образом, переиспользовать и упорядочивать существующие элементы, необходимо указать уникальный атрибут key
для каждого элемента:
template
<div v-for="item in items" :key="item.id">
<!-- Содержимое -->
</div>
При использовании <template v-for>
атрибут key
нужно устанавливать на контейнер <template>
:
template
<template v-for="todo in todos" :key="todo.name">
<li>{{ todo.name }}</li>
</template>
Примечание
key
в данном случае — специальный атрибут, связываемый через v-bind
. Его не следует путать с переменной с именем key при использовании v-for
с объектами.
Рекомендуется всегда указывать атрибут key
с v-for
, кроме случаев когда итерируемое содержимое DOM простое (т.е. не содержит компонентов или элементов DOM с состоянием), или когда сознательно полагаетесь на стратегию обновления по умолчанию для улучшения производительности.
Привязка key
ожидает использования примитивных значений — строк и чисел. Не используйте объекты в качестве ключей для v-for
. Подробное использование атрибута key
описано в документации API.
v-for
и компоненты
Эта секция подразумевает, что уже знакомы с компонентами. Не стесняйтесь пропустить её сейчас и вернуться потом.
Можно использовать v-for
на компонентах, как на обычных элементах (не забывайте указать key
):
template
<MyComponent v-for="item in items" :key="item.id" />
Однако в компонент никакие данные автоматически передаваться не будут, поскольку у каждого будет своя изолированная область видимости. Чтобы передать итерируемые данные в компонент потребуется явно использовать входные параметры:
template
<MyComponent
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id"
/>
Причина, почему не происходит автоматической передачи item
в компонент заключается в том, что это сделает компонент жёстко связанным с тем, как работает v-for
. Но явное указание на то, откуда поступают данные, позволит переиспользовать и в других ситуациях.
Посмотрите этот пример простого TODO-списка, чтобы увидеть, как отрисовать список компонентов с помощью v-for
, передавая разные данные каждому экземпляру.
Отслеживание изменений в массивах
Методы изменения, мутирующие массив
Vue способен определять, когда вызываются методы мутации реактивного массива, и запускать необходимые обновления. К таким мутирующим методам относятся:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
Методы изменения с заменой массива
Методы, мутирующие массив, как следует из названия, будут изменять исходный массив, на котором они вызваны. Но есть и другие, например filter()
, concat()
и slice()
, которые не мутируют исходный массив, а всегда возвращают новый массив. При их использовании можно просто заменять старый массив на новый:
js
// `items` это ref-ссылка с массивом в значении
items.value = items.value.filter((item) => item.message.match(/Foo/))
Может показаться, что в таких случаях Vue придётся выбросить существующий DOM и заново отрисовать весь список — к счастью, это не так. Во Vue есть умные эвристики для максимизации переиспользования элементов DOM, поэтому замена одного массива другим, в случае совпадения части элементов этих массивов, будет очень эффективной операцией.
Отображение отфильтрованных/отсортированных результатов
Иногда может потребоваться отображать отфильтрованную или отсортированную версию массива, сохранив оригинальные данные. В таком случае можно создать вычисляемое свойство, которое будет возвращать отфильтрованный или отсортированный массив.
Например:
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>
В ситуациях, когда вычисляемые свойства невозможно применить (например, внутри вложенных циклов v-for
), можно использовать метод:
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>
Обратите внимание на использование reverse()
и sort()
в вычисляемых свойствах! Эти два метода изменят исходный массив, а этого следует избегать в геттерах вычисляемых свойств. Поэтому перед вызовом этих методов создайте копию исходного массива:
diff
- return numbers.reverse()
+ return [...numbers].reverse()