Слоты
Props передают в компонент данные, а слоты — разметку. Компонент оставляет в своём шаблоне место, тег slot, а родитель решает, что туда вставить. Так делают обёртки: карточку, модальное окно, список с заголовком.
Слот по умолчанию
Создайте src/components/BaseCard.vue:
<!-- src/components/BaseCard.vue -->
<template>
<section class="card">
<slot />
</section>
</template>
<style scoped>
.card {
border: 1px solid #ddd;
border-radius: 8px;
padding: 16px;
}
</style>Всё, что родитель напишет между открывающим и закрывающим тегом компонента, окажется на месте slot:
<!-- src/App.vue -->
<script setup>
import BaseCard from './components/BaseCard.vue'
</script>
<template>
<BaseCard>
<h2>Сегодня</h2>
<p>Купить хлеб, позвонить Анне.</p>
</BaseCard>
</template>На странице появится рамка со скруглёнными углами, а внутри — заголовок и абзац из App.vue.
Содержимое по умолчанию
Текст внутри тега slot показывается, если родитель ничего не передал:
<template>
<section class="card">
<slot>Пусто</slot>
</section>
</template><BaseCard /> без содержимого выведет рамку со словом «Пусто».
Именованные слоты
Если мест для вставки несколько, им дают имена. Создайте src/components/TodoList.vue:
<!-- src/components/TodoList.vue -->
<template>
<div class="todo-list">
<header><slot name="header" /></header>
<ul><slot /></ul>
<footer><slot name="footer" /></footer>
</div>
</template>Родитель указывает слот через template с директивой v-slot или её короткой формой #:
<!-- src/App.vue, фрагмент шаблона -->
<TodoList>
<template #header>
<h2>Мои задачи</h2>
</template>
<TodoItem v-for="todo in todos" :key="todo.id" :todo="todo" />
<template #footer>
Осталось: {{ remaining }}
</template>
</TodoList>Заголовок попадёт в header, задачи — в слот по умолчанию внутри ul, счётчик — в footer.
Слоты с данными
Иногда компонент перебирает данные сам, а вид строки задаёт родитель. Компонент передаёт данные в слот атрибутами:
<!-- src/components/TodoList.vue -->
<script setup>
defineProps({ todos: Array })
</script>
<template>
<ul>
<li v-for="todo in todos" :key="todo.id">
<slot :todo="todo" />
</li>
</ul>
</template><!-- src/App.vue, фрагмент шаблона -->
<TodoList :todos="todos" v-slot="{ todo }">
<strong v-if="todo.done">{{ todo.text }}</strong>
<span v-else>{{ todo.text }}</span>
</TodoList>Выполненные задачи выводятся жирным, остальные — обычным текстом. Родитель получил todo из компонента через v-slot.
Частая ошибка
Ждать, что содержимое слота видит переменные дочернего компонента. Разметка в слоте пишется в шаблоне родителя и имеет доступ только к данным родителя. Если внутри слота нужна переменная компонента, компонент должен явно передать её атрибутом, как :todo="todo" выше.