Slots (слоты) в Vue 3 предоставляют мощный механизм для создания гибких и переиспользуемых компонентов. В этой статье мы разберём все способы передачи параметров в слоты с примерами из реальной практики.
Основные типы слотов в Vue 3
- Слоты по умолчанию — базовый способ передачи контента
- Именованные слоты — для точного позиционирования контента
- Scoped slots — с передачей параметров из дочернего компонента
1. Scoped Slots (Основной способ передачи параметров)
<!-- Дочерний компонент -->
<template>
<div>
<slot :item="item" :index="index"></slot>
</div>
</template>
<!-- Родительский компонент -->
<ChildComponent>
<template v-slot:default="slotProps">
{{ slotProps.item }} - {{ slotProps.index }}
</template>
</ChildComponent>
2. Именованные scoped slots
<!-- Дочерний компонент -->
<template>
<div>
<slot name="header" :title="title"></slot>
<slot name="content" :data="contentData"></slot>
</div>
</template>
<!-- Родительский компонент -->
<DataContainer>
<template #header="{ title }">
<h2>{{ title }}</h2>
</template>
<template #content="{ data }">
<p>{{ data.description }}</p>
</template>
</DataContainer>
3. Динамические параметры слотов
<!-- Дочерний компонент -->
<script setup>
const slotProps = computed(() => ({
user: currentUser.value,
timestamp: new Date()
}))
</script>
<template>
<slot v-bind="slotProps"></slot>
</template>
4. Деструктуризация параметров
<template #item="{ id, name }">
<div>{{ id }}: {{ name }}</div>
</template>
Практические примеры
Пример 1: Гибкий список
<SmartList :items="users">
<template #item="{ user }">
<UserCard :user="user" />
</template>
</SmartList>
Пример 2: Модальное окно с параметрами
<ModalDialog>
<template #header="{ close }">
<button @click="close">×</button>
</template>
<template #default="{ data }">
{{ data.message }}
</template>
</ModalDialog>
Лучшие практики
- Используйте осмысленные имена для параметров слотов
- Для сложных компонентов документируйте структуру слотов
- Избегайте глубокой вложенности scoped slots
- Используйте TypeScript для типизации параметров
Заключение
Scoped slots в Vue 3 предоставляют мощный инструмент для создания гибких компонентов. Освоив передачу параметров в слоты, вы сможете создавать по-настоящему переиспользуемые UI-компоненты.
Для более сложных сценариев рассмотрите использование Composition API вместе со слотами.
Vue 3 Composition API + Slots: Сложные сценарии использования
В сочетании с Composition API слоты Vue 3 раскрывают свою настоящую мощь. Рассмотрим продвинутые паттерны для сложных UI-компонентов.
1. Динамические слоты с реактивными параметрами
<!-- DynamicTable.vue -->
<script setup>
import { ref, computed } from 'vue'
const props = defineProps(['data'])
const sortDirection = ref('asc')
const sortedData = computed(() => {
return [...props.data].sort((a, b) => {
return sortDirection.value === 'asc'
? a.value - b.value
: b.value - a.value
})
})
</script>
<template>
<table>
<slot name="header" :toggleSort="() => sortDirection = sortDirection === 'asc' ? 'desc' : 'asc'"></slot>
<tr v-for="(item, index) in sortedData" :key="index">
<slot :item="item" :index="index"/>
</tr>
</table>
</template>
Применение: Таблицы с сортировкой, где логика инкапсулирована в компоненте, а рендеринг контролируется через слоты.
2. Состояние модальных окон
<!-- useModal.js -->
import { ref } from 'vue'
export function useModal() {
const isOpen = ref(false)
const open = () => isOpen.value = true
const close = () => isOpen.value = false
return { isOpen, open, close }
}
<!-- ModalComponent.vue -->
<script setup>
import { useModal } from './useModal'
const modal = useModal()
</script>
<template>
<slot :open="modal.open" :close="modal.close" :isOpen="modal.isOpen"/>
</template>
Использование:
<ModalComponent v-slot="{ open, close, isOpen }">
<button @click="open">Открыть</button>
<div v-if="isOpen" class="modal">
<slot name="content"/>
<button @click="close">Закрыть</button>
</div>
</ModalComponent>
3. Композиционные слоты для форм
<!-- useFormField.js -->
import { ref, computed } from 'vue'
export function useFormField(initialValue) {
const value = ref(initialValue)
const isValid = computed(() => value.value.length > 0)
return { value, isValid }
}
<!-- FormField.vue -->
<script setup>
import { useFormField } from './useFormField'
const props = defineProps(['initialValue'])
const field = useFormField(props.initialValue)
</script>
<template>
<div class="form-field" :class="{ invalid: !field.isValid }">
<slot :value="field.value" :isValid="field.isValid"/>
</div>
</template>
Использование с кастомным input:
<FormField initialValue="" v-slot="{ value, isValid }">
<input
v-model="value"
:class="{ error: !isValid }"
placeholder="Введите текст"
>
<span v-if="!isValid">Поле обязательно</span>
</FormField>
4. Сложные компоненты данных
<!-- DataFetcher.vue -->
<script setup>
import { ref, onMounted } from 'vue'
const props = defineProps(['url'])
const data = ref(null)
const error = ref(null)
const isLoading = ref(false)
onMounted(async () => {
isLoading.value = true
try {
const response = await fetch(props.url)
data.value = await response.json()
} catch (err) {
error.value = err
} finally {
isLoading.value = false
}
})
</script>
<template>
<slot
:data="data"
:error="error"
:isLoading="isLoading"
:reload="onMounted"
/>
</template>
Использование:
<DataFetcher url="/api/users" v-slot="{ data, isLoading }">
<div v-if="isLoading">Загрузка...</div>
<UserList v-else :users="data" />
</DataFetcher>
5. Продвинутый пример: Компонент вкладок
<!-- TabsContainer.vue -->
<script setup>
import { ref } from 'vue'
const activeTab = ref(0)
const tabs = ref([])
const registerTab = (title) => {
const id = tabs.value.length
tabs.value.push({ id, title })
return id
}
const setActiveTab = (id) => {
activeTab.value = id
}
</script>
<template>
<div class="tabs-container">
<div class="tabs-header">
<button
v-for="tab in tabs"
@click="setActiveTab(tab.id)"
:class="{ active: activeTab === tab.id }"
>
{{ tab.title }}
</button>
</div>
<div class="tabs-content">
<slot :activeTab="activeTab"/>
</div>
</div>
</template>
Использование:
<TabsContainer v-slot="{ activeTab }">
<template #default>
<Tab :register="registerTab" title="Профиль">
<div v-if="activeTab === 0">...</div>
</Tab>
<Tab :register="registerTab" title="Настройки">
<div v-if="activeTab === 1">...</div>
</Tab>
</template>
</TabsContainer>
Заключение
Сочетание Composition API и слотов позволяет создавать:
- Полностью инкапсулированную логику компонентов
- Максимально гибкие API для переиспользуемых компонентов
- Сложные stateful-компоненты с простым интерфейсом
- Легко тестируемые решения (бизнес-логика отделена от рендеринга)
Добавить комментарий