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

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

Автор

Олег Марков

Введение

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

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


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

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

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

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

// Мы проходимся по массиву items, для каждого элемента создается

  • с выводом поля 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?
    О: Добавьте обработчик прямо в шаблон слота — он будет работать в контексте родительского компонента. Например: vue <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 и на этапе использования слота явно указывайте типы: vue <MyComp> <template #default="{ item }: { item: MyType }"> <!-- ... --> </template> </MyComp>

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

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

    Стрелочка влевоУправление переменными и реактивными свойствами во VueТипизация и использование TypeScript в VuejsСтрелочка вправо

    Все гайды по Vue

    Работа с пользовательскими интерфейсами и UI библиотеками во VueОрганизация и структура исходных файлов в проектах VueОбзор популярных шаблонов и стартовых проектов на VueКак организовать страницы и маршруты в проекте на VueСоздание серверных приложений на Vue с помощью Nuxt jsРабота со стилями и CSS в Vue js для красивых интерфейсовСоздание и структурирование Vue.js приложенияНастройка и сборка проектов Vue с использованием современных инструментов
    Управление переменными и реактивными свойствами во VueИспользование v for и slot в VueТипизация и использование TypeScript в VuejsИспользование шаблонов в Vue js для построения интерфейсовПередача данных между компонентами с помощью props в Vue jsУправление property и функциями во Vue.jsОсновы работы с объектами в VueПонимание жизненного цикла компонента Vue js на примере mountedИспользование метода map в Vue для обработки массивовОбработка пользовательского ввода в Vue.jsОрганизация файлов и структура проекта Vue.jsКомпоненты Vue создание передача данных события и emitИспользование директив и их особенности на Vue с помощью defineСоздание и использование компонентов в Vue JSОбработка кликов и пользовательских событий в Vue
    Открыть базу знаний