Отрисовка списков
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
. Фактически, вы можете использовать деструктуризацию псевдонима элемента v-for
аналогично деструктуризации аргументов функции:
template
<li v-for="{ message } in items">
{{ message }}
</li>
<!-- с псевдонимом index -->
<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: 'Как делать списки во Vue',
author: 'Джейн Доу',
publishedAt: '2016-04-10'
})
template
<ul>
<li v-for="value in myObject">
{{ value }}
</li>
</ul>
Вы также можете указать второй псевдоним для имени свойства (например, key
):
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
также может принимать целое число. В этом случае он будет повторять шаблон m
раз, основываясь на диапазоне 1...n
.
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
:
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>
Примечание
Не рекомендуется использовать v-if
и v-for
для одного и того же элемента из-за неявного приоритета.
Есть два распространённых случая, когда это может показаться заманчивым:
Для фильтрации элементов в списке (например,
v-for="user in users" v-if="user.isActive"
). В таких случаях заменитеusers
на новое вычисляемое свойство, которое возвращает отфильтрованный список (например,activeUsers
).Чтобы избежать отрисовки списка, если он должен быть скрыт (например,
v-for="user in users" v-if="shouldShowUsers"
). В таких случаях переместитеv-if
на элемент-контейнер (например,ul
илиol
).
Сохранение состояния с помощью key
Когда Vue обновляет список элементов, отображаемых с помощью v-for
, по умолчанию он использует стратегию «исправления на месте». Если порядок элементов данных изменился, вместо перемещения элементов DOM в соответствии с порядком элементов Vue исправит каждый элемент на месте и убедится, что он отражает то, что должно отображаться по этому конкретному индексу.
Этот режим по умолчанию эффективен, но подходит только в том случае, если выходные данные отрисовки списка не зависят от состояния дочернего компонента или временного состояния DOM (например, входных значений формы).
Чтобы дать Vue подсказку, позволяющую отслеживать идентичность каждого узла и, таким образом, повторно использовать и изменять порядок существующих элементов, вам необходимо предоставить уникальный атрибут key
для каждого элемента:
template
<div v-for="item in items" :key="item.id">
<!-- content -->
</div>
При использовании <template v-for>
ключ key
должен быть помещен в контейнер <template>
:
template
<template v-for="todo in todos" :key="todo.name">
<li>{{ todo.name }}</li>
</template>
Примечание
key
— это специальный атрибут, связанный с v-bind
. Его не следует путать с переменной имени свойства при использовании v-for
с объектом.
Рекомендуется предоставлять атрибут key
с v-for
всегда, когда это возможно, если только итерируемое содержимое DOM не является простым (т. е. не содержит компонентов или элементов DOM с состоянием), либо вы намеренно полагаетесь на поведение по умолчанию для повышения производительности.
Привязка key
ожидает примитивные значения, то есть строки и числа. Не используйте объекты в качестве клавиш v-for
. Подробную информацию об использовании атрибута key
см. в документации по API key
.
v-for
с компонентом
Этот раздел предполагает знание Компонентов. Не стесняйтесь пропустить его и вернуться позже.
Вы можете напрямую использовать v-for
на компоненте, как на любом обычном элементе (не забудьте указать key
):
template
<MyComponent v-for="item in items" :key="item.id" />
Однако это не приведёт к автоматической передаче данных компоненту, поскольку компоненты имеют собственные изолированные области видимости. Чтобы передать итерированные данные в компонент, мы также должны использовать props
:
template
<MyComponent
v-for="(item, index) in items"
:item="item"
:index="index"
:key="item.id"
/>
Причина отказа от автоматической инжекции item
в компонент заключается в том, что это делает компонент тесно связанным с тем, как работает v-for
. Явное указание на то, откуда берутся данные, делает компонент пригодным для повторного использования в других ситуациях.
Посмотрите этот пример простого списка дел чтобы посмотреть, как вывести список компонентов с помощью v-for
, передавая каждому экземпляру разные данные.
Обнаружение изменений в массиве
Мутационные методы
Vue умеет определять, когда вызываются методы мутации реактивного массива, и запускать необходимые обновления. К таким методам мутации относятся:
push()
pop()
shift()
unshift()
splice()
sort()
reverse()
Замена массива
Методы мутации, как следует из названия, мутируют исходный массив, на котором они вызываются. Для сравнения, существуют и немутирующие методы, например filter()
, concat()
и slice()
, которые не изменяют исходный массив, но всегда возвращают новый массив. При работе с немутирующими методами мы должны заменить старый массив на новый:
js
// `items` - ссылка со значением массива
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()