Vue Front-end Инженер

Vue Front-end Инженер

Роадмап навыков для прокачки

Рендеринг списков: v-for и атрибут key

VueJSTemplate SyntaxДирективы

Основная идея

v-for — директива для рендеринга списков во Vue. Она создаёт элементы на основе массива или объекта. Атрибут :key необходим для корректной идентификации элементов при обновлении списка.

Ключевые аспекты

  • Итерация по массивуv-for="item in items" или v-for="(item, index) in items"
  • Итерация по объектуv-for="(value, key, index) in object"
  • Итерация по диапазонуv-for="n in 10" создаст 10 элементов
  • Обязательный key — уникальный идентификатор для каждого элемента
  • Работа с template — группировка нескольких элементов в цикле

Плюсы

  • Декларативный синтаксис для рендеринга списков
  • Автоматическое обновление при изменении данных
  • Оптимизация рендеринга через key
  • Поддержка деструктуризации в выражениях

Минусы

  • Требуется уникальный key для оптимальной производительности
  • Нельзя комбинировать с v-if на одном элементе
  • Использование index как key может привести к багам

Частые ошибки на собеседованиях

  • Не понимают зачем нужен key (для идентификации элементов при reconciliation)
  • Используют index как key для динамических списков (приводит к багам при сортировке/удалении)
  • Путают синтаксис in и of (оба работают одинаково)
  • Не знают, что v-for может работать с объектами и числами, не только с массивами

Введение и проблематика

Отображение списков — одна из базовых задач в веб-разработке:

  • Список товаров в каталоге
  • Посты в ленте
  • Элементы меню навигации
  • Строки таблицы

Vue предоставляет директиву v-for для декларативного рендеринга коллекций данных.

Атрибут key критически важен для производительности и корректной работы Vue. Он помогает Vue понять, какие элементы изменились, добавились или удалились.


Базовая теория

Итерация по массиву

vue
<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      {{ item.name }}
    </li>
  </ul>
</template>
 
<script setup>
import { ref } from 'vue'
 
const items = ref([
  { id: 1, name: 'Vue' },
  { id: 2, name: 'React' },
  { id: 3, name: 'Angular' }
])
</script>

Доступ к индексу

vue
<template>
  <ul>
    <li v-for="(item, index) in items" :key="item.id">
      {{ index + 1 }}. {{ item.name }}
    </li>
  </ul>
</template>

Деструктуризация

vue
<template>
  <div v-for="{ id, name, email } in users" :key="id">
    <h3>{{ name }}</h3>
    <p>{{ email }}</p>
  </div>
</template>
 
<script setup>
import { ref } from 'vue'
 
const users = ref([
  { id: 1, name: 'Иван', email: 'ivan@mail.ru' },
  { id: 2, name: 'Мария', email: 'maria@mail.ru' }
])
</script>

Практические примеры

Итерация по объекту

vue
<template>
  <ul>
    <!-- Только значения -->
    <li v-for="value in profile" :key="value">
      {{ value }}
    </li>
 
    <!-- Значение и ключ -->
    <li v-for="(value, key) in profile" :key="key">
      {{ key }}: {{ value }}
    </li>
 
    <!-- Значение, ключ и индекс -->
    <li v-for="(value, key, index) in profile" :key="key">
      {{ index }}. {{ key }}: {{ value }}
    </li>
  </ul>
</template>
 
<script setup>
import { reactive } from 'vue'
 
const profile = reactive({
  name: 'Иван',
  age: 25,
  city: 'Москва'
})
</script>

Итерация по диапазону чисел

vue
<template>
  <!-- Создаёт 5 элементов (n от 1 до 5) -->
  <span v-for="n in 5" :key="n">{{ n }} </span>
 
  <!-- Результат: 1 2 3 4 5 -->
</template>

Группировка с template

vue
<template>
  <table>
    <template v-for="user in users" :key="user.id">
      <tr>
        <td>{{ user.name }}</td>
        <td>{{ user.email }}</td>
      </tr>
      <tr>
        <td colspan="2">{{ user.bio }}</td>
      </tr>
    </template>
  </table>
</template>

Атрибут key

Зачем нужен key

vue
<template>
  <!-- ❌ Не рекомендуется -->
  <li v-for="item in items">
    {{ item.name }}
  </li>
</template>
  • Vue использует алгоритм «по месту» (in-place patch)
  • При изменении порядка состояние компонентов не сохраняется
  • Могут быть баги с анимациями и формами

Правила для key

vue
<template>
  <!-- ✅ Уникальный идентификатор из данных -->
  <li v-for="user in users" :key="user.id">
 
  <!-- ✅ Уникальная комбинация -->
  <li v-for="item in items" :key="`${item.type}-${item.id}`">
 
  <!-- ❌ Index для динамических списков -->
  <li v-for="(item, index) in items" :key="index">
 
  <!-- ❌ Неуникальные значения -->
  <li v-for="item in items" :key="item.name">
</template>
⚠️

Используйте index как key только для статических списков, которые не будут изменяться (сортировка, фильтрация, добавление в середину).


Пограничные кейсы

v-if и v-for

🚫

Не используйте v-if и v-for на одном элементе. v-if имеет приоритет выше и не видит переменные из v-for.

vue
<template>
  <!-- ❌ Неправильно -->
  <li v-for="user in users" v-if="user.isActive" :key="user.id">
    {{ user.name }}
  </li>
 
  <!-- ✅ Правильно: используйте computed -->
  <li v-for="user in activeUsers" :key="user.id">
    {{ user.name }}
  </li>
 
  <!-- ✅ Правильно: оберните в template -->
  <template v-for="user in users" :key="user.id">
    <li v-if="user.isActive">
      {{ user.name }}
    </li>
  </template>
</template>
 
<script setup>
import { ref, computed } from 'vue'
 
const users = ref([
  { id: 1, name: 'Иван', isActive: true },
  { id: 2, name: 'Мария', isActive: false }
])
 
const activeUsers = computed(() =>
  users.value.filter(user => user.isActive)
)
</script>

Вложенные циклы

vue
<template>
  <div v-for="category in categories" :key="category.id">
    <h3>{{ category.name }}</h3>
    <ul>
      <li v-for="item in category.items" :key="item.id">
        {{ item.name }}
      </li>
    </ul>
  </div>
</template>

Мутации массива

Vue отслеживает мутирующие методы массива и реактивно обновляет DOM:

vue
<script setup>
import { ref } from 'vue'
 
const items = ref(['A', 'B', 'C'])
 
// Эти методы вызовут обновление
items.value.push('D')      // добавить в конец
items.value.pop()          // удалить с конца
items.value.shift()        // удалить с начала
items.value.unshift('Z')   // добавить в начало
items.value.splice(1, 1)   // удалить/вставить
items.value.sort()         // сортировка
items.value.reverse()      // реверс
</script>

Для замены массива присвойте новое значение:

vue
<script setup>
// Фильтрация — создаёт новый массив
items.value = items.value.filter(item => item.isActive)
 
// Маппинг
items.value = items.value.map(item => ({ ...item, processed: true }))
</script>

Плюсы и минусы

Аспектv-for с keyБез key
ПроизводительностьОптимальнаяМожет быть хуже
Состояние компонентовСохраняетсяТеряется
Сортировка спискаКорректнаяБаги
АнимацииРаботаютМогут сломаться
Формы в спискеКорректныеДанные путаются

Вопросы интервьюера

Q: Зачем нужен атрибут key?

Key помогает Vue идентифицировать элементы при обновлении списка. Без key Vue использует алгоритм «по месту», который может привести к багам при изменении порядка или удалении элементов.

Q: Можно ли использовать index как key?

Только для статических списков. Для динамических (с сортировкой, фильтрацией, добавлением) нужен уникальный идентификатор из данных.

Q: Почему нельзя использовать v-if и v-for вместе?

v-if имеет более высокий приоритет и вычисляется первым, не имея доступа к переменным из v-for. Используйте computed или оберните в template.

Q: Какие методы массива вызывают обновление?

Мутирующие методы: push, pop, shift, unshift, splice, sort, reverse. Они отслеживаются Vue и вызывают ререндер.


Источники