Отображение массива элементов с помощью v-for
Для рендинга списка элементов на основе массива данных используйте директиву v-for, в которой используется особый синтаксис: записи item in items, где items - массив данных, а item - ссылка на текущий элемент массива.
<ul id="example-1">
<li v-for="item in items">
{{ item.message }}
</li>
</ul>
var example1 = new Vue({
el: '#example-1',
data: {
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
Внутри блока v-for полностью доступны свойства из области видимости родителя. v-for поддерживает необязательный второй параметр для указания индекса текущего элемента.
<ul id="example-2">
<li v-for="(item, index) in items">
{{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
</ul>
var example2 = new Vue({
el: '#example-2',
data: {
parentMessage: 'Родитель',
items: [
{ message: 'Foo' },
{ message: 'Bar' }
]
}
})
Вместо in разделителем может быть слово of:
<div v-for="item of items"></div>
v-for для объекта
v-for можно использовать для итерирования по свойствам объекта:
<ul id="v-for-object" class="demo">
<li v-for="value in object">
{{ value }}
</li>
</ul>
new Vue({
el: '#v-for-object',
data: {
object: {
firstName: 'Иван',
lastName: 'Петров',
age: 30
}
}
})
Для получения ключей можно указать второй аргумент:
<div v-for="(value, key) in object">
{{ key }}: {{ value }}
</div>
Третий параметр для получения индексов:
<div v-for="(value, key, index) in object">
{{ index }}. {{ key }}: {{ value }}
</div>
key
При обновлении списка элементов, управляемой директивой v-for, используется "обновление на месте". Если порядок элементов массива или объекта изменился, элементы DOM не будут перемещаться, а будет обновлен каждый элемент, чтобы он отображал новые данные по индексу.
Это режим, используемый по умолчанию, эффективен, но применим в случаях, когда результат рендинга списка не полагается на состояние дочерних компонентов или временных состояний DOM.
Что Vue понимал, как отслеживать идентичность каждого элемента, позволяющую переиспользовать и перемещать существующие элементы, нужно указать атрибут key для каждого элемента. Отлично будет использовать уникальный id.
<div v-for="item in items" :key="item.id">
<!-- содержимое -->
</div>
Лучше указывать key с v-for, за исключением случаев когда итерируемый контент DOM прост или когда вы используете стратегию "обновление на месте" по умолчанию для производительности.
Отслеживание изменений в массивах
Методы внесения изменений
Vue оборачивает методы внесения изменений наблюдаемого массива так, чтобы они вызывали обновления представления. Оборачиваются следующие методы:
- push()
- pop()
- shift()
- unshift()
- splice()
- sort()
- reverse()
Замены в массиве
Методы внесения изменений, изменяют оригинальный массив, на котором они вызываются. Есть и неизменяющие методы, такие как filter(), concat() и slice(), не вносящие изменений в изначальный массив, а всегда возвращающие новый массив. В неизменяющихся методах можно просто заменить старый массив новым:
example1.items = example1.items.filter(function (item) {
return item.message.match(/Foo/)
})
Умные эвристики Vue обеспечивает максимизацию переиспользования элементов DOM, поэтому замена одного массива другим, когда совпадают части элементов в этих массивах, очень эффективная операция.
Предостережения
Ограничения JavaScript, не позволяют Vue заметить следующие изменения в массиве:
- Прямую установку элемента по индексу, например: vm.items[indexOfItem] = newValue
- Явное изменение длины массива, например: vm.items.length = newLength
Первая проблема решается двумя способами, которые дают эффект аналогичный vm.items[indexOfItem] = newValue, а также инициируют реактивные обновления состояния приложения:
// Vue.set
Vue.set(example1.items, indexOfItem, newValue)
// Array.prototype.splice
example1.items.splice(indexOfItem, 1, newValue)
Для обхода второй проблемы используется splice:
example1.items.splice(newLength)
Предостережения об изменениях объектов
Снова, из-за ограничений JavaScript, Vue не может обнаружить добавление или удаление свойств. Например:
var vm = new Vue({
data: {
a: 1
}
})
// свойство `vm.a` реактивно
vm.b = 2
// свойство `vm.b` НЕ реактивно
Vue не позволяет динамически добавлять новые реактивные свойства на корневом уровне уже созданного экземпляра. НО, может добавлять реактивные свойства к вложенному объекту с помощью метода Vue.set(object, key, value).
var vm = new Vue({
data: {
userProfile: {
name: 'Anika'
}
}
})
Можно добавить новое свойство age для вложенного объекта userProfile:
Vue.set(vm.userProfile, 'age', 27)
Также можно использовать метод экземпляра vm.$set, который является псевдонимом для глобального Vue.set:
vm.$set(this.userProfile, 'age', 27)
Для добавления ряда новых свойств существующему объекту, допустим, используя Object.assign() или _.extend(), нужно создать новый объект со свойствами обоих объектов. Так что, вместо:
Object.assign(this.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
Добавлять новые реактивные свойства нужно вот так:
this.userProfile = Object.assign({}, this.userProfile, {
age: 27,
favoriteColor: 'Vue Green'
})
Отображение отфильтрованных/отсортированных результатов
Для отображения отфильтрованной или отсортированной версии массива, не изменяя оригинала данных, нужно создать вычисляемое свойство, которое будет возвращать отфильтрованный или отсортированный массив.
<li v-for="n in evenNumbers">{{ n }}</li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
computed: {
evenNumbers: function () {
return this.numbers.filter(function (number) {
return number % 2 === 0
})
}
}
Когда вычисляемые свойства не пригодны к использованию, в случае внутри вложенных циклов v-for, можно использовать метод:
<li v-for="n in even(numbers)">{{ n }}</li>
data: {
numbers: [ 1, 2, 3, 4, 5 ]
},
methods: {
even: function (numbers) {
return numbers.filter(function (number) {
return number % 2 === 0
})
}
}
v-for и диапазоны
В v-for можно передать целое число и шаблон будет повторён указанное число раз.
<div>
<span v-for="n in 10">{{ n }} </span>
</div>
v-for и <template>
Подобно шаблону v-if, можно использовать тег <template> с директивой v-for для отображения блока из нескольких элементов.
<ul>
<template v-for="item in items">
<li>{{ item.msg }}</li>
<li class="divider"></li>
</template>
</ul>
v-for и v-if
Если v-for и v-if указаны на одном элементе, то v-for будет иметь больший приоритет, чем v-if. То есть, v-if выполнится отдельно для каждой итерации цикла.
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>
В результате отобразятся todo, которые ещё не выполнены.
Чтобы по условию пропускалось выполнение всего цикла, нужно поместить v-if на внешний элемент или на <template>.
<ul v-if="shouldRenderTodos">
<li v-for="todo in todos">
{{ todo }}
</li>
</ul>
Компоненты и v-for
Можно использовать v-for и на пользовательских компонентах, так же, как и на обычных элементах:
<my-component v-for="item in items" :key="item.id"></my-component>
Но, это не передаст в компонент никаких данных автоматически, так как компоненты имеют изолированные области видимости. Чтобы передать итерируемые данные в компонент нужно явно использовать входные параметры:
<my-component
v-for="(item, index) in items"
v-bind:item="item"
v-bind:index="index"
v-bind:key="item.id"
></my-component>
Данные в item не передаются в компонент автоматически, потому что это сделает компонент жестко связанным с логикой работы v-for. Если источник данных указывается явно, компонент можно использовать в дальнейшем.
<div id="todo-list-example">
<input
v-model="newTodoText"
v-on:keyup.enter="addNewTodo"
placeholder="Добавить todo"
>
<ul>
<li
is="todo-item"
v-for="(todo, index) in todos"
v-bind:key="todo.id"
v-bind:title="todo.title"
v-on:remove="todos.splice(index, 1)"
></li>
</ul>
</div>
Vue.component('todo-item', {
template: '\
<li>\
{{ title }}\
<button v-on:click="$emit(\'remove\')">X</button>\
</li>\
',
props: ['title']
})
new Vue({
el: '#todo-list-example',
data: {
newTodoText: '',
todos: [
{
id: 1,
title: 'Вымыть посуду'
},
{
id: 2,
title: 'Вынести мусор'
},
{
id: 3,
title: 'Подстричь газон'
}
],
nextTodoId: 4
},
methods: {
addNewTodo: function () {
this.todos.push({
id: this.nextTodoId++,
title: this.newTodoText
})
this.newTodoText = ''
}
}
})
0 комментариев