5 Способов Композиции Компонентов во Vue: От Слотов до Renderless

от автора

в ,
Время чтения: 3 мин.

Хотите создавать гибкие и переиспользуемые компоненты во 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 = мощная абстракция.

Композиция компонентов делает код чище и удобнее для масштабирования. 🚀


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Сколько будет 2 + 8?