Что такое слоты во Vue: default, именованные и scoped?
Вопрос
Что такое слоты во Vue, чем отличаются default-слот, именованные слоты и scoped-слоты?
Короткий ответ
Слоты — это механизм, позволяющий родительскому компоненту передавать разметку (а не только данные) внутрь дочернего компонента, а дочерний компонент решает, куда её вставить. Default-слот — один безымянный слот для основного содержимого; именованные слоты позволяют передавать несколько независимых кусков разметки в разные места шаблона; scoped-слоты дополнительно дают возможность дочернему компоненту передать свои данные обратно в содержимое слота, которое рендерит родитель.
Подробный ответ
Если props передают данные, а events — сигналы о событиях, то слоты передают разметку. Дочерний компонент объявляет в своём шаблоне место <slot>, куда будет вставлено содержимое, переданное родителем между открывающим и закрывающим тегом компонента.
- Default-слот.
<slot></slot>в дочернем компоненте — родитель просто пишет содержимое между тегами:<Card>Текст</Card>. Если родитель ничего не передал, можно указать содержимое по умолчанию внутри<slot>Заглушка</slot>. - Именованные слоты (
named slots). Когда нужно несколько независимых мест для вставки (например, шапка/тело/подвал карточки), слоты именуются:<slot name="header" />. Родитель указывает, куда какая разметка идёт, через<template #header>. - Scoped-слоты. Обычно данные текут от родителя к содержимому слота, но иногда именно дочерний компонент владеет данными, которые нужно показать в кастомном виде (например, компонент списка знает текущий элемент итерации, а как его отрисовать — решает родитель). Дочерний компонент передаёт данные через атрибуты на
<slot>:<slot :item="item" />. Родитель получает их через<template #default="{ item }">, деструктурируя объект слот-пропсов.
Слоты — ключевой инструмент для написания переиспользуемых, гибких по разметке компонентов (модалки, карточки, списки, layout'ы), где сама структура и логика едины, а конкретное содержимое каждый раз разное.
Пример
<!-- BaseCard.vue: именованные + scoped слоты -->
<script setup>
defineProps({
items: { type: Array, required: true }
})
</script>
<template>
<div class="card">
<header class="card__header">
<!-- именованный слот с содержимым по умолчанию -->
<slot name="header">Без заголовка</slot>
</header>
<ul class="card__body">
<!-- scoped slot: дочерний компонент отдаёт item и index наружу -->
<li v-for="(item, index) in items" :key="item.id">
<slot :item="item" :index="index">
{{ item.name }} <!-- поведение по умолчанию -->
</slot>
</li>
</ul>
</div>
</template>
<!-- Parent.vue -->
<script setup>
import BaseCard from './BaseCard.vue'
const users = [
{ id: 1, name: 'Alice', active: true },
{ id: 2, name: 'Bob', active: false }
]
</script>
<template>
<BaseCard :items="users">
<template #header>
<h2>Список пользователей</h2>
</template>
<!-- scoped-слот: получаем item из дочернего компонента -->
<template #default="{ item }">
<strong :class="{ online: item.active }">{{ item.name }}</strong>
</template>
</BaseCard>
</template>
Дополнительные вопросы
- Чем
v-slot(#) отличается от обычногоv-bind, и почему у слотов своя область видимости? - Как проверить, передал ли родитель содержимое в конкретный слот (
$slots)? - В каком случае вместо scoped-слота лучше использовать обычный проп-функцию (render prop)?