Подробное руководство по использованию Scoped Slots в Vue 3
Scoped slots (локальные слоты) в Vue.js — это мощный инструмент для создания гибких и переиспользуемых компонентов. Они позволяют передавать данные из дочернего компонента в родительский, предоставляя родительскому компоненту контроль над тем, как будет отображаться содержимое слота.
В этом руководстве мы подробно рассмотрим:
- Что такое слоты и scoped slots.
- Синтаксис и использование в Vue 3.
- Различия между Vue 2 и Vue 3 в контексте scoped slots.
- Примеры использования.
- Практические советы и лучшие практики.
1. Что такое слоты и Scoped Slots
Обычные слоты
Слоты — это места в шаблоне компонента, которые заполняются содержимым, переданным из родительского компонента.
Пример:
<!-- Родительский компонент -->
<template>
<MyComponent>
<p>Это содержимое слота.</p>
</MyComponent>
</template>
<!-- Дочерний компонент (MyComponent.vue) -->
<template>
<div>
<slot></slot>
</div>
</template>
Scoped Slots
Scoped slots позволяют дочернему компоненту передавать данные в слот, которые родительский компонент может использовать.
Зачем нужны Scoped Slots?
Они позволяют:
- Делать компоненты более гибкими и переиспользуемыми.
- Давать родительскому компоненту контроль над тем, как отображать данные, предоставленные дочерним компонентом.
2. Синтаксис и использование в Vue 3
В дочернем компоненте
Дочерний компонент предоставляет данные в слот через атрибут v-slot
(или просто #
), передавая объект в slot
с помощью v-bind
.
Пример:
<!-- Дочерний компонент (MyComponent.vue) -->
<template>
<div>
<slot :user="userData"></slot>
</div>
</template>
<script>
export default {
data() {
return {
userData: {
name: 'Иван',
age: 30,
},
};
},
};
</script>
В родительском компоненте
Родительский компонент принимает данные из слота и использует их в своем шаблоне.
Пример:
<!-- Родительский компонент -->
<template>
<MyComponent>
<template #default="slotProps">
<p>Имя пользователя: {{ slotProps.user.name }}</p>
<p>Возраст пользователя: {{ slotProps.user.age }}</p>
</template>
</MyComponent>
</template>
<script>
import MyComponent from './MyComponent.vue';
export default {
components: {
MyComponent,
},
};
</script>
Пояснение:
#default
— это сокращенный синтаксис дляv-slot:default
.slotProps
— объект, переданный из дочернего компонента черезv-bind
.
3. Различия между Vue 2 и Vue 3
Изменения в синтаксисе
- В Vue 2 использовался синтаксис
slot-scope
, который был признан громоздким и менее интуитивным. - В Vue 3 рекомендуется использовать директиву
v-slot
или ее сокращение#
.
Пример в Vue 2:
<!-- Родительский компонент в Vue 2 -->
<MyComponent>
<template slot="default" slot-scope="slotProps">
<!-- использование slotProps -->
</template>
</MyComponent>
Пример в Vue 3:
<!-- Родительский компонент в Vue 3 -->
<MyComponent>
<template #default="slotProps">
<!-- использование slotProps -->
</template>
</MyComponent>
Удаление slot-scope
- В Vue 3
slot-scope
больше не используется. - Вместо этого используется
v-slot
и его сокращения.
4. Примеры использования
Пример 1: Компонент списка с настраиваемым отображением элементов
Дочерний компонент (ItemList.vue
):
<template>
<ul>
<li v-for="item in items" :key="item.id">
<slot :item="item">
<!-- Значение по умолчанию, если слот не предоставлен -->
{{ item.name }}
</slot>
</li>
</ul>
</template>
<script>
export default {
props: {
items: {
type: Array,
required: true,
},
},
};
</script>
Родительский компонент:
<template>
<ItemList :items="products">
<template #default="{ item }">
<strong>{{ item.name }}</strong> - Цена: {{ item.price }} руб.
</template>
</ItemList>
</template>
<script>
import ItemList from './ItemList.vue';
export default {
components: {
ItemList,
},
data() {
return {
products: [
{ id: 1, name: 'Товар 1', price: 100 },
{ id: 2, name: 'Товар 2', price: 200 },
],
};
},
};
</script>
Пояснение:
- Дочерний компонент передает каждый
item
в слот. - Родительский компонент определяет, как отобразить каждый
item
.
Пример 2: Форма с настраиваемыми элементами ввода
Дочерний компонент (FormWrapper.vue
):
<template>
<form @submit.prevent="handleSubmit">
<slot :formData="formData" :updateField="updateField"></slot>
<button type="submit">Отправить</button>
</form>
</template>
<script>
export default {
data() {
return {
formData: {},
};
},
methods: {
updateField(field, value) {
this.formData[field] = value;
},
handleSubmit() {
this.$emit('submit', this.formData);
},
},
};
</script>
Родительский компонент:
<template>
<FormWrapper @submit="submitForm">
<template #default="{ formData, updateField }">
<label>
Имя:
<input type="text" @input="updateField('name', $event.target.value)" />
</label>
<label>
Email:
<input type="email" @input="updateField('email', $event.target.value)" />
</label>
</template>
</FormWrapper>
</template>
<script>
import FormWrapper from './FormWrapper.vue';
export default {
components: {
FormWrapper,
},
methods: {
submitForm(data) {
console.log('Отправка данных формы:', data);
},
},
};
</script>
Пояснение:
- Дочерний компонент предоставляет методы и данные для управления формой.
- Родительский компонент определяет, какие поля будут в форме и как они будут выглядеть.
5. Практические советы и лучшие практики
Именованные слоты
Вы можете определять несколько слотов с разными именами.
Дочерний компонент:
<template>
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot> <!-- Слот по умолчанию -->
</main>
<footer>
<slot name="footer"></slot>
</footer>
</template>
Родительский компонент:
<template>
<MyComponent>
<template #header>
<h1>Заголовок</h1>
</template>
<template #default>
<p>Основное содержимое</p>
</template>
<template #footer>
<p>Подвал</p>
</template>
</MyComponent>
</template>
Деструктуризация пропсов слота
Вы можете использовать деструктуризацию для удобства.
<template>
<MyComponent>
<template #default="{ user: { name, age } }">
<p>Имя: {{ name }}</p>
<p>Возраст: {{ age }}</p>
</template>
</MyComponent>
</template>
Использование render-функций
В некоторых случаях может быть удобно использовать render-функции для более гибкого управления слотами.
Пример:
// В родительском компоненте
export default {
render() {
return h(MyComponent, {}, {
default: ({ item }) => h('div', {}, item.name),
});
},
};
Предоставление действий через scoped slots
Вы можете передавать не только данные, но и функции.
Дочерний компонент:
<template>
<div>
<slot :count="count" :increment="increment"></slot>
</div>
</template>
<script>
export default {
data() {
return {
count: 0,
};
},
methods: {
increment() {
this.count++;
},
},
};
</script>
Родительский компонент:
<template>
<Counter>
<template #default="{ count, increment }">
<p>Счетчик: {{ count }}</p>
<button @click="increment">Увеличить</button>
</template>
</Counter>
</template>
Заключение
Scoped slots в Vue 3 являются мощным инструментом для создания гибких и переиспользуемых компонентов. Они позволяют компонентам быть более абстрактными, предоставляя при этом возможность настраивать их поведение и отображение из родительских компонентов.
Ключевые моменты:
- Используйте
v-slot
или его сокращение#
для определения слотов. - Передавайте данные из дочернего компонента в родительский через
v-bind
в<slot>
. - В родительском компоненте получайте данные через переменные слота в
template
.
Лучшие практики:
- Ясно именуйте пропсы слотов, чтобы облегчить понимание кода.
- Избегайте глубокой вложенности, чтобы не усложнять структуру приложения.
- Документируйте API ваших компонентов, если они предоставляют слоты, чтобы другие разработчики понимали, как ими пользоваться.
Используя scoped slots, вы сможете создавать более модульные и адаптируемые компоненты, что облегчит разработку и поддержку вашего приложения на Vue.js.