Отображение массива элементов с помощью 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 = ''
    }
  }
})