Хотите создавать гибкие и переиспользуемые компоненты во Vue? В этой статье разбираем 5 мощных подходов к композиции: от базовых слотов до продвинутых renderless-компонентов
🔹 Слоты — для кастомизации разметки
🔹 Scoped Slots — передача данных в родительский компонент
🔹 Provide/Inject — глобальные состояния без пропсов
🔹 Renderless-компоненты — чистая логика без привязки к вёрстке
🔹 Динамические компоненты — переключение views на лету
🔹 1. Слоты (Slots) — Базовый способ композиции
Позволяют вставлять контент из родительского компонента в дочерний.
Пример: Компонент-обёртка (Card.vue
)
<!-- Card.vue -->
<template>
<div class="card">
<header class="card-header">
<slot name="header">Заголовок по умолчанию</slot>
</header>
<div class="card-body">
<slot></slot> <!-- Контент по умолчанию -->
</div>
</div>
</template>
Использование:
<template>
<Card>
<template #header>
<h2>Мой заголовок</h2>
</template>
<p>Контент карточки</p>
</Card>
</template>
✅ Когда использовать:
- Когда нужно передать разметку в дочерний компонент.
- Для создания переиспользуемых UI-компонентов (карточки, модалки, аккордеоны).
🔹 2. Scoped Slots (Слоты с данными)
Позволяют передавать данные из дочернего компонента в родительский.
Пример: Компонент-список (List.vue
)
<!-- List.vue -->
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item"></slot>
</li>
</ul>
</template>
<script setup>
defineProps(['items']);
</script>
Использование:
<template>
<List :items="users">
<template #default="{ item }">
{{ item.name }} ({{ item.email }})
</template>
</List>
</template>
✅ Когда использовать:
- Когда нужно кастомное отображение данных (например, разный UI для одних и тех же данных).
- В компонентах-списках, таблицах, выпадающих меню.
🔹 3. Компоненты-провайдеры (Provide/Inject)
Позволяет передавать данные глубоко в дерево компонентов без пропсов.
Пример: ThemeProvider
<!-- ThemeProvider.vue -->
<template>
<slot></slot>
</template>
<script setup>
import { provide, ref } from 'vue';
const theme = ref('light');
const toggleTheme = () => {
theme.value = theme.value === 'light' ? 'dark' : 'light';
};
provide('theme', { theme, toggleTheme });
</script>
Использование в любом дочернем компоненте:
<script setup>
import { inject } from 'vue';
const { theme, toggleTheme } = inject('theme');
</script>
✅ Когда использовать:
- Для глобальных состояний (темы, настройки, авторизация).
- Чтобы избежать «пропс-дриллинга» (передачи данных через множество компонентов).
🔹 4. Компоненты-композиты (Renderless Components)
Компоненты без своей разметки, только логика.
Пример: MouseTracker
(логика отслеживания мыши)
<!-- MouseTracker.vue -->
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
const x = ref(0);
const y = ref(0);
const update = (e) => {
x.value = e.clientX;
y.value = e.clientY;
};
onMounted(() => window.addEventListener('mousemove', update));
onUnmounted(() => window.removeEventListener('mousemove', update));
</script>
<template>
<slot :x="x" :y="y"></slot>
</template>
Использование:
<template>
<MouseTracker v-slot="{ x, y }">
Координаты мыши: {{ x }}, {{ y }}
</MouseTracker>
</template>
✅ Когда использовать:
- Когда нужно отделить логику от представления.
- Для переиспользуемой логики (анимации, API-запросы, геолокация).
🔹 5. Динамические компоненты (<component :is>
) + KeepAlive
Позволяет переключаться между компонентами динамически.
Пример: Табы (Tabs)
<template>
<button @click="currentTab = 'Tab1'">Tab 1</button>
<button @click="currentTab = 'Tab2'">Tab 2</button>
<KeepAlive>
<component :is="currentTab" />
</KeepAlive>
</template>
<script setup>
import Tab1 from './Tab1.vue';
import Tab2 from './Tab2.vue';
const currentTab = ref('Tab1');
</script>
✅ Когда использовать:
- Для SPA-навигации без перезагрузки страницы.
- В табах, модалках, пошаговых формах.
🔥 Итог: какой подход выбрать?
Метод | Когда использовать? | Примеры применения |
---|---|---|
Слоты | Передача разметки | Карточки, модалки |
Scoped Slots | Кастомное отображение данных | Списки, таблицы |
Provide/Inject | Глобальные состояния | Тема, авторизация, локаль |
Renderless | Логика без UI | Анимации, API-запросы |
Динамические | Переключение компонентов | Табы, SPA-роутинг |
Лучшие практики:
- Избегайте избыточных пропсов — если передаёте много данных, возможно, стоит использовать
provide/inject
или хранилище (Pinia). - Декомпозируйте компоненты — если компонент слишком сложный, разбейте его на мелкие.
- Комбинируйте подходы — например,
scoped slots
+renderless
= мощная абстракция.
Композиция компонентов делает код чище и удобнее для масштабирования. 🚀
Добавить комментарий