логотип PurpleSchool
логотип PurpleSchool

Использование v for и slot в Vue

Автор

Олег Марков

Введение

Если вы работаете с Vue, скорее всего, сталкивались с необходимостью рендерить списки данных и создавать переиспользуемые компоненты, содержащие произвольное содержимое. Для этих задач во Vue есть два очень мощных инструмента — директива v-for и слоты (slot). Каждый из них заслуживает отдельного внимания, но в этой статье мы не только подробно рассмотрим, как они работают сами по себе, но и разберемся, как их комбинировать для создания максимально гибких и эффективных компонентов.

v-for позволяет интенсивно использовать динамику данных, показывая повторяющиеся элементы на основе массивов или объектов. Слоты, в свою очередь, обеспечивают компонентам гибкость в принятии дополнительного контента, который внедряет родительский компонент. Совместное использование этих инструментов поможет вам делать масштабируемый, читаемый и легко поддерживаемый код.

В Vue директива v-for и элементы <slot> являются мощными инструментами для создания динамических списков и гибких компонентов. Эффективное использование позволяет создавать переиспользуемый код, который легко адаптируется к различным потребностям. Однако, чтобы полностью раскрыть потенциал этих инструментов, необходимо глубокое понимание их работы и нюансов. Если вы хотите детальнее погрузиться в тонкости работы с v-for, именованными слотами и проектированию гибких компонентов — приходите на наш большой курс Vue.js 3, Vue Router и Pinia. На курсе 173 уроков и 21 упражнение, AI-тренажеры для безлимитной практики с кодом и задачами 24/7, решение задач с живым ревью наставника, еженедельные встречи с менторами.

v-for: основы работы

Синтаксис v-for

В Vue директива v-for применяется для циклического повторения элементов DOM по массиву или объекту. Смотрите, как это выглядит на практике:

<ul>
  <li v-for="item in items" :key="item.id">
    {{ item.name }} 
  </li>
</ul>

// Мы проходимся по массиву `items`, для каждого элемента создается <li> с выводом поля name. Атрибут :key нужен для правильной работы системы виртуального DOM.

Давайте чуть подробнее разберем параметры v-for:

<div v-for="(value, index) in array" :key="index">
  {{ index }}: {{ value }}
</div>

// Здесь `index` — порядковый номер в массиве, а `value` — текущее значение. Указание :key — крайне важная практика.

Работать с объектами через v-for

Vue позволяет использовать v-for и для объектов. Например:

<ul>
  <li v-for="(value, key) in object" :key="key">
    {{ key }}: {{ value }}
  </li>
</ul>

// Мы проходим по объекту `object`, получая ключ и значение каждой пары.

:key — почему это важно

Ключ (:key) помогает Vue более эффективно и корректно отслеживать изменения в списке и перестраивать DOM. Обычно в качестве ключа используют уникальный идентификатор элемента. Если уникального поля нет, используйте индекс, но делайте это только если список гарантированно не будет меняться во время жизни компонента.

slot: как устроена система слотов во Vue

Обычные (дефолтные) слоты

Слоты позволяют прокидывать содержимое внутрь компонента. Смотрите:

<!-- App.vue -->
<custom-card>
  <template #default>
    <p>Содержимое карточки</p>
  </template>
</custom-card>
<!-- components/CustomCard.vue -->
<template>
  <div class="card">
    <slot>
      <!-- Здесь выводится все, что размещено между тегами <custom-card> в родителе -->
    </slot>
  </div>
</template>

// Это обычный слот: если родитель ничего не передаст, выведется содержимое по умолчанию (если оно указано).

Именованные слоты

Если вам нужно иметь несколько областей для подстановки разного контента, используйте именованные слоты. Покажу вам пример:

<custom-dialog>
  <template #header>
    <h3>Заголовок</h3>
  </template>
  <template #body>
    <p>Основная часть контента</p>
  </template>
  <template #footer>
    <button>Закрыть</button>
  </template>
</custom-dialog>
<!-- CustomDialog.vue -->
<template>
  <div class="dialog">
    <header>
      <slot name="header" />
    </header>
    <main>
      <slot name="body" />
    </main>
    <footer>
      <slot name="footer" />
    </footer>
  </div>
</template>

// Родитель может передать разные фрагменты в разные области компонента.

Комбинирование v-for и slot

Динамические списки с кастомным содержимым

Одна из сильнейших сторон Vue — возможность рендерить списки компонентов и предоставлять им разнообразное содержимое с помощью слотов.

Давайте посмотрим, как использовать v-for в комбинации со слотами внутри компонента.

Пример: Список карточек с шаблонным содержимым

<!-- CardList.vue (компонент-контейнер) -->
<template>
  <ul>
    <li v-for="item in items" :key="item.id">
      <card-item>
        <template #default>
          {{ item.text }}
        </template>
      </card-item>
    </li>
  </ul>
</template>
<script setup>
const props = defineProps(['items'])
</script>
<!-- CardItem.vue -->
<template>
  <div class="item">
    <slot />
  </div>
</template>

// Каждый card-item получает свой фрагмент и выводит его через слот.

v-for внутри компонента-слота

Можно использовать v-for и внутри компонента, который содержит слот — например, делать делегирование обработки списка самому компоненту. Смотрите вариант:

<!-- ListRenderer.vue -->
<template>
  <ul>
    <li v-for="(item, i) in items" :key="item.id">
      <slot :item="item" :index="i">
        <!-- Слот будет заменен предоставленным шаблоном, если он есть, иначе выводится по умолчанию -->
        {{ item.name }}
      </slot>
    </li>
  </ul>
</template>
<script setup>
const props = defineProps(['items'])
</script>
<!-- Использование ListRenderer -->
<ListRenderer :items="myArray">
  <template #default="{ item, index }">
    <strong>{{ index + 1 }}.</strong> {{ item.customLabel }}
  </template>
</ListRenderer>

// Обратите внимание: слот принимает пропсы, переданные через :item и :index — это так называемый scoped slot (слот с доступом к данным).

Scoped slots: передача данных из дочернего компонента

Когда компоненты используют слот, иногда родительскому компоненту полезно получить данные из дочернего — например, элемент списка. Для этого используют scoped slots.

Как работают scoped slots

В дочернем компоненте объявляем слот и передаем в него данные:

// В дочернем компоненте
<slot :item="item" :index="i" />

В родителе можем получить эти данные следующим образом:

<MyListComponent :items="myItems">
  <template #default="{ item, index }">
    <!-- Здесь доступны item и index -->
    <div>
      {{ index }}: {{ item.label }}
    </div>
  </template>
</MyListComponent>

// Таким образом, родитель не только подменяет часть шаблона, но и получает доступ к переменным, которые предоставляет дочерний компонент.

Примеры сложных сценариев использования

Список карточек с кастомным заголовком и содержимым

Разберем ситуацию, когда карточка имеет отдельные именованные слоты для заголовка и основного содержимого, а список карточек формирует общий компонент:

<!-- CardsList.vue -->
<template>
  <div>
    <Card v-for="card in cards" :key="card.id">
      <template #header>
        <span>{{ card.title }}</span>
      </template>
      <template #default>
        <p>{{ card.body }}</p>
      </template>
    </Card>
  </div>
</template>
<script setup>
const props = defineProps(['cards'])
</script>
<!-- Card.vue -->
<template>
  <div class="card">
    <div class="header">
      <slot name="header" />
    </div>
    <div class="body">
      <slot />
    </div>
  </div>
</template>

// Здесь компонент CardsList циклично выводит компонент Card с разным содержимым в слотах header и default.

Генерация form-полей с помощью v-for и slot

Теперь покажу пример, как можно использовать slots и v-for для динамического создания формы:

<!-- FormFieldsRenderer.vue -->
<template>
  <form>
    <div v-for="field in fields" :key="field.name">
      <label :for="field.name">{{ field.label }}</label>
      <slot :field="field">
        <!-- по умолчанию рендерим input -->
        <input :id="field.name" :name="field.name" :type="field.type" />
      </slot>
    </div>
  </form>
</template>
<script setup>
const props = defineProps(['fields'])
</script>
<!-- Использовать компонент можно так -->
<FormFieldsRenderer :fields="fieldsArray">
  <template #default="{ field }">
    <!-- примеры условных кастомизаций для некоторых полей -->
    <input v-if="field.type === 'text'" :id="field.name" :name="field.name" />
    <select v-else-if="field.type === 'select'" :id="field.name" :name="field.name">
      <option v-for="opt in field.options" :value="opt.value" :key="opt.value">{{ opt.label }}</option>
    </select>
    <input v-else :type="field.type" :id="field.name" :name="field.name" />
  </template>
</FormFieldsRenderer>

// Смотрите, как таким способом родитель не только меняет шаблон, но и динамически управляет содержимым поля.

Ограничения и особенности

Как избежать ловушек

  • Не используйте индекс как key, когда список может изменяться — это приведет к багам.
  • Комбинируя v-for + slot, избегайте ситуации, когда состояние компонента зависит только от внешнего индекса.
  • Важно учитывать, что slot не обязательны к заполнению. Если не передать шаблон в слот — он будет пустым или выведет дефолтный контент.
  • Scoped slot не поддерживают двустороннюю реактивность напрямую — данные, переданные через :item, всегда «только для чтения» на стороне родителя.

Слоты не рендерятся вне компонента

Помните, если разместить v-for вне компонента, обращаться к slot внутри этого цикла не получится.

Когда лучше применять v-for и slot вместе

  • Для сложных визуальных списков, где родитель должен контролировать не только структуру, но и мелкие особенности каждого элемента.
  • Если компоненты должны быть максимально переиспользуемыми и легко настраиваться за счет разнообразных шаблонов.
  • Когда элементы списка должны принимать разные варианты содержимого, исходя из данных или внешних условий.

Заключение

v-for и slot — инструменты Vue, которыми вы будете пользоваться практически в каждом проекте. Используйте v-for, чтобы рендерить динамические списки и работать с коллекциями. Применяйте слоты для гибкой вставки произвольного содержимого внутрь компонента. Совмещайте эти две техники, чтобы делать мощные, масштабируемые и легко настраиваемые интерфейсы. Благодаря им код становится лаконичным, а компоненты действительно переиспользуемыми.

Помимо v-for и слотов, важным аспектом при создании динамических компонентов является управление состоянием и маршрутизацией. Pinia и Vue Router предлагают элегантные решения для этих задач, упрощая разработку сложных приложений. Чтобы узнать больше о работе с этими инструментами и научиться применять их на практике, рекомендуем ознакомиться с курсом Vue.js 3, Vue Router и Pinia. В первых 3 модулях уже доступно бесплатное содержание — начните погружаться в мир Vue прямо сейчас.

Частозадаваемые технические вопросы по теме статьи и ответы на них

В: Как передать обработчик событий в слот с помощью v-for?
О: Добавьте обработчик прямо в шаблон слота — он будет работать в контексте родительского компонента. Например: js <MyList :items="data"> <template #default="{ item }"> <div @click="handler(item)">{{ item.name }}</div> </template> </MyList> handler должен быть объявлен у родителя.

В: Можно ли использовать v-if и v-for одновременно в одном элементе?
О: Можно, но порядок имеет значение. Vue рекомендует v-for выносить "наружу", то есть использовать v-if внутри v-for, чтобы фильтровать уже перебираемые элементы, а не наоборот.

В: Как типизировать данные, приходящие в scoped slot, если я использую TypeScript?
О: В используйте defineProps и на этапе использования слота явно указывайте типы: js <MyComp> <template #default="{ item }: { item: MyType }"> <!-- ... --> </template> </MyComp>

В: Почему после обновления данных список не перерисовывается?
О: Проверьте, что у каждого элемента уникальный key. Если вы используете объекты, меняйте массив реактивно (через методы массива, а не прямое присваивание).

В: Можно ли использовать slot внутри v-for, но не в компоненте, а прямо в одноуровневом списке?
О: Нет, слот работает только как часть компонента. Если требуется подобная динамика, оберните логику в отдельный компонент и используйте slot внутри него.

Стрелочка влевоУправление переменными и реактивными свойствами во VueПрименение v-bind для динамической привязки атрибутов в VueСтрелочка вправо

Постройте личный план изучения Vue до уровня Middle — бесплатно!

Vue — часть карты развития Frontend

  • step100+ шагов развития
  • lessons30 бесплатных лекций
  • lessons300 бонусных рублей на счет

Бесплатные лекции

Все гайды по Vue

Руководство по валидации форм во Vue.jsИнтеграция Tiptap для создания редакторов на VueРабота с таблицами во Vue через TanStackИнструкция по установке и компонентам Vue sliderУправление пакетами Vue js с помощью npmУправление пакетами и node modules в Vue проектахКак использовать meta для улучшения SEO на VueПолный гайд по компоненту messages во Vuejs5 правил использования Inertia с Vue и LaravelРабота с модулями и пакетами в VueИнструкция по работе с grid на VueGithub для Vue проектов - подробная инструкция по хранению и совместной работеНастройка ESLint для Vue проектов и поддержка качества кодаОбработка ошибок и отладка в Vue.jsИспользование Vue Devtools для отладки и мониторинга приложенийРабота с конфигурационными файлами и скриптами VueСоздание и настройка проектов Vue с помощью Vue CLI3 способа интеграции Chart.js с Vue для создания графиковРабота с Canvas во VueИнструкция по реализации календаря во VueРабота с Ant Design Vue для создания UI на Vue
Обзор и использование утилит Vue для удобной разработкиРабота с обновлениями компонента и жизненным циклом updateРазрешение конфликтов и ошибок с помощью Vue resolveИспользование query-параметров и их обработка в маршрутах VueЗагрузка и управление состоянием загрузки в VueИспользование библиотек Vue для расширения функционалаРабота с JSON данными в приложениях VueКак работать с экземплярами компонента Instance во VueПолучение данных и API-запросы во Vue.jsЭкспорт и импорт данных и компонентов в VueОбработка событий и их передача между компонентами VuejsГайд по defineEmits на Vue 3Понимание core функционала Vue и его применениеПонимание и применение Composition API в Vue 3Понимание и работа с компилятором VueКогда и как использовать $emit и call во VueВзаимодействие с внешними API через Axios в Vue
Веб приложения на Vue архитектура и лучшие практикиИспользование Vite для быстрого старта и сборки проектов на Vue 3Работа с URL и ссылками в приложениях на VueРабота с пользовательскими интерфейсами и UI библиотеками во VueОрганизация и структура исходных файлов в проектах VueИспользование Quasar Framework для разработки на Vue с готовыми UI-компонентамиОбзор популярных шаблонов и стартовых проектов на VueИнтеграция Vue с PHP для создания динамичных веб-приложенийКак организовать страницы и маршруты в проекте на VueNuxt JS и Vue 3 для SSR приложенийСоздание серверных приложений на Vue с помощью Nuxt jsИспользование Vue Native для разработки мобильных приложенийОрганизация и управление индексной страницей в проектах VueИспользование Docker для контейнеризации приложений на VueИнтеграция Vue.js с Django для создания полноценных веб-приложенийСоздание и работа с дистрибутивом build dist Vue приложенийРабота со стилями и CSS в Vue js для красивых интерфейсовСоздание и структурирование Vue.js приложенияКак исправить ошибку cannot find module vueНастройка и сборка проектов Vue с использованием современных инструментовИнтеграция Vue с Bitrix для корпоративных решенийРазработка административных панелей на Vue js
5 библиотек для создания tree view во VueИнтеграция Tailwind CSS с Vue для современных интерфейсовИнтеграция Vue с серверной частью и HTTPS настройкамиКак обрабатывать async операции с Promise во VueИнтеграция Node.js и Vue.js для разработки приложенийРуководство по интеграции Vue js в NET проектыПримеры использования JSX во VueГайд по импорту и регистрации компонентов на VueМногоязычные приложения на Vue с i18nИнтеграция FLIR данных с Vue5 примеров использования filter во Vue для упрощения разработки3 примера реализации drag-and-drop во Vue
Управление переменными и реактивными свойствами во VueИспользование v for и slot в VueПрименение v-bind для динамической привязки атрибутов в VueУправление пользователями и их данными в Vue приложенияхСоздание и использование UI Kit для Vue приложенийТипизация и использование TypeScript в VuejsИспользование шаблонов в Vue js для построения интерфейсовИспользование Swiper для создания слайдеров в VueРабота со стилями и стилизацией в VueСтруктура и особенности Single File Components SFC в VueРабота со SCSS в проектах на Vue для стилизацииРабота со скроллингом и прокруткой в Vue приложенияхПрименение script setup синтаксиса в Vue 3 для упрощения компонентовИспользование scoped стилей для изоляции CSS в компонентах Vue3 способа улучшить навигацию Vue с push()Обработка запросов и асинхронных операций в VueПонимание и использование provide inject для передачи данных между компонентамиПередача и использование props в Vue 3 для взаимодействия компонентовПередача данных между компонентами с помощью props в Vue jsУправление property и функциями во Vue.jsРабота со свойствами компонентов VueУправление параметрами и динамическими данными во VueРабота с lifecycle-хуком onMounted во VueОсновы работы с объектами в VueПонимание жизненного цикла компонента Vue js на примере mountedИспользование модальных окон modal в Vue приложенияхИспользование методов в компонентах Vue для обработки логикиИспользование метода map в Vue для обработки массивовИспользование хуков жизненного цикла Vue для управления состоянием компонентаРабота с ключами key в списках и компонентах VueОбработка пользовательского ввода в Vue.jsРабота с изображениями и их оптимизация в VueИспользование хуков жизненного цикла в VueОрганизация сеток и гридов для верстки интерфейсов на VueСоздание и управление формами в VueОрганизация файлов и структура проекта Vue.jsКомпоненты Vue создание передача данных события и emitРабота с динамическими компонентами и данными в Vue3 способа манипулирования DOM на VueРуководство по div во VueИспользование директив в Vue и их расширенные возможностиОсновы и применение директив в VueИспользование директив и их особенности на Vue с помощью defineИспользование компонентов datepicker в Vue для выбора датОрганизация циклов и итераций во VueКак работает компиляция Vue CoreСоздание и использование компонентов в Vue JSОбработка кликов и пользовательских событий в VueИспользование классов в Vue для организации кода и компонентовИспользование директивы checked для управления состоянием чекбоксов в VueГайд на checkbox компонент во VueОтображение данных в виде графиков с помощью Vue ChartСоздание и настройка кнопок в VueСоздание и настройка кнопок в Vue приложенияхРабота с lifecycle-хуками beforeCreate и beforeMount во VueИспользование массивов и методов их обработки в VueИспользование массивов и их обработка в Vue
Использование Vuetify для создания современных интерфейсов на VueИспользование transition во VueТестирование компонентов и приложений на VueРабота с teleport для управления DOM во VueПять шагов по настройке SSR в VuejsИспользование Shadcn UI компонентов с Vue для продвинутых интерфейсовИспользование router-link для навигации в Vue RouterКак использовать require в Vue для динамического импорта модулейРабота с динамическим рендерингом и виртуальным DOM на Vue.jsИспользование ref для управления ссылками и реактивностью в Vue 3Использование Vue Pro и его преимущества для профессиональной разработкиРуководство по nextTick для работы с DOMСоздание и использование компонентов с помощью Vue js и CУправление состоянием и реактивностью через inject и provideДинамическое обновление компонентов и данных на VueГлубокое изучение документации Vue и как эффективно её использоватьИспользование Crystal с Vue для разработкиИспользование вычисляемых свойств для динамического отображения данных на Vue jsОптимизация производительности и предупреждения в Vue
Открыть базу знаний

Лучшие курсы по теме

изображение курса

Vue 3 и Pinia

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.9
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

TypeScript с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее
изображение курса

Next.js - с нуля

Антон Ларичев
AI-тренажеры
Практика в студии
Гарантия
Бонусы
иконка звёздочки рейтинга4.7
3 999 ₽ 6 990 ₽
Подробнее

Отправить комментарий