Skip to content

Отрисовка списков

Отображение элементов массива через v-for

Используйте директиву v-for для отрисовки списка элементов на основе массива данных. У директивы v-for специальный синтаксис: item in items, где items — исходный массив, а itemссылка на итерируемый элемент массива:

js
const items = ref([{ message: 'Foo' }, { message: 'Bar' }])
js
data() {
  return {
    items: [{ 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' }])
js
data() {
  return {
    parentMessage: 'Родитель',
    items: [{ message: 'Foo' }, { message: 'Bar' }]
  }
}
template
<li v-for="(item, index) in items">
  {{ parentMessage }} - {{ index }} - {{ item.message }}
</li>
  • Родитель - 0 - Foo
  • Родитель - 1 - Bar
  • Область видимости переменных в 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.keys():

    js
    const myObject = reactive({
      title: 'How to do lists in Vue',
      author: 'Jane Doe',
      publishedAt: '2016-04-10'
    })
    js
    data() {
      return {
        myObject: {
          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, передавая разные данные каждому экземпляру.

    Посмотрите этот пример простого 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/))
    js
    this.items = this.items.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)
    })
    js
    data() {
      return {
        numbers: [1, 2, 3, 4, 5]
      }
    },
    computed: {
      evenNumbers() {
        return this.numbers.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)
    }
    js
    data() {
      return {
        sets: [[ 1, 2, 3, 4, 5 ], [6, 7, 8, 9, 10]]
      }
    },
    methods: {
      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()
    Отрисовка списковУже загружено