Vue 3 Slots передача параметров

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-компоненты с простым интерфейсом
  • Легко тестируемые решения (бизнес-логика отделена от рендеринга)

Комментарии

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

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