Как создать компонент с неограниченным кол-вом слотов?
Комментарии (2)
Ответ сгенерирован нейросетью и может содержать ошибки
Как создать компонент с неограниченным количеством слотов
Что такое слоты
Слоты (slots) — это механизм Vue для вставки контента из родительского компонента в дочерний. Они позволяют сделать компоненты гибкими и переиспользуемыми.
Виды слотов
1. Обычный слот
// CardComponent.vue
<template>
<div class="card">
<slot></slot>
</div>
</template>
// Родитель
<template>
<Card>
<p>Этот контент попадёт в слот</p>
</Card>
</template>
2. Именованные слоты (Named Slots)
// CardComponent.vue
<template>
<div class="card">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
</template>
// Родитель
<template>
<Card>
<template #header>
<h1>Заголовок</h1>
</template>
<p>Содержимое по умолчанию</p>
<template #footer>
<button>Закрыть</button>
</template>
</Card>
</template>
Проблема: неограниченное кол-во слотов
Если вам нужно компонент с динамическим количеством слотов (например, до 100), есть несколько подходов.
Решение 1: Использовать default slot с component prop
После компонент, который принимает компоненты как параметры:
// FlexibleLayout.vue
<template>
<div class="layout">
<component
v-for="item in sections"
:key="item.id"
:is="item.component"
:props="item.props"
/>
</div>
</template>
<script>
export default {
props: {
sections: {
type: Array,
required: true
}
}
};
</script>
// Использование
<template>
<FlexibleLayout :sections="sections" />
</template>
<script>
export default {
data() {
return {
sections: [
{ id: 1, component: Header },
{ id: 2, component: Content },
{ id: 3, component: Sidebar },
{ id: 4, component: Footer }
]
};
}
};
</script>
Решение 2: Использовать $slots с рендер-функцией
Обратитесь к слотам через $slots и рендерьте их динамически:
// DynamicSlotComponent.vue
<template>
<div class="container">
<template v-for="(slotContent, slotName) in $slots" :key="slotName">
<div class="slot-wrapper">
<slot :name="slotName"></slot>
</div>
</template>
</div>
</template>
<script>
export default {
// Автоматически поддерживает все слоты
};
</script>
// Использование
<template>
<DynamicSlotComponent>
<template #section1>
<h2>Раздел 1</h2>
</template>
<template #section2>
<h2>Раздел 2</h2>
</template>
<template #section3>
<h2>Раздел 3</h2>
</template>
<!-- Можно добавлять слоты динамически -->
</DynamicSlotComponent>
</template>
Решение 3: Массив объектов с контентом (рекомендуется)
Самый гибкий подход — передать массив данных и рендерить его:
// GridLayout.vue
<template>
<div class="grid">
<div
v-for="item in items"
:key="item.id"
class="grid-item"
>
<slot
:name="`item-${item.id}`"
:item="item"
>
<!-- Контент по умолчанию -->
<p>{{ item.title }}</p>
</slot>
</div>
</div>
</template>
<script>
export default {
props: {
items: {
type: Array,
required: true
}
}
};
</script>
// Использование
<template>
<GridLayout :items="items">
<template #item-1="{ item }">
<Card>{{ item.title }}</Card>
</template>
<template #item-2="{ item }">
<Article>{{ item.content }}</Article>
</template>
<!-- По умолчанию для остальных items используется контент из слота -->
</GridLayout>
</template>
<script>
export default {
data() {
return {
items: [
{ id: 1, title: "Первый" },
{ id: 2, title: "Второй" },
{ id: 3, title: "Третий" },
// ... сотни элементов
]
};
}
};
</script>
Решение 4: Использовать render-функцию (Advanced)
Для максимальной гибкости можно использовать render-функцию:
// DynamicRenderer.vue
<script>
export default {
props: {
sections: Array
},
render() {
return (
<div class="renderer">
{this.sections.map((section, index) => (
<div key={section.id} class="section">
{this.$slots[`section-${section.id}`]?.() ||
<p>{section.defaultContent}</p>
}
</div>
))}
</div>
);
}
};
</script>
// Использование
<template>
<DynamicRenderer :sections="sections">
<template #section-1>
<Header />
</template>
<template #section-2>
<Main />
</template>
</DynamicRenderer>
</template>
Практический пример: Card Gallery
// CardGallery.vue
<template>
<div class="gallery">
<div
v-for="card in cards"
:key="card.id"
class="card"
>
<!-- Слот для каждой карточки -->
<slot
:name="`card-${card.id}`"
:card="card"
>
<!-- Контент по умолчанию -->
<div class="card-default">
<h3>{{ card.title }}</h3>
<p>{{ card.description }}</p>
</div>
</slot>
</div>
</div>
</template>
<script>
export default {
props: {
cards: {
type: Array,
default: () => []
}
}
};
</script>
// Использование
<template>
<CardGallery :cards="products">
<template #card-1="{ card }">
<ProductCard :product="card" />
</template>
<template #card-2="{ card }">
<PremiumCard :product="card" />
</template>
<!-- Остальные карточки используют контент по умолчанию -->
</CardGallery>
</template>
<script>
export default {
data() {
return {
products: [
{ id: 1, title: "Товар 1" },
{ id: 2, title: "Товар 2" },
{ id: 3, title: "Товар 3" },
// ... может быть тысячи товаров
]
};
}
};
</script>
Лучшие практики
- Используй prop-based approach для количественно больших наборов данных
- Используй именованные слоты когда нужна гибкость в укрытии контента
- Предусмотри контент по умолчанию в слотах
- Использовать scoped slots для передачи данных в контент слота
- Избегай большого кол-ва явных слотов в шаблоне
Вывод
Для создания компонента с неограниченным количеством слотов лучше всего использовать prop-based approach с массивом данных и scoped slots для кастомизации. Это обеспечивает гибкость, производительность и чистоту кода. Render-функции подходят для более сложных случаев, но усложняют отладку.