Продвинутые механизмы взаимодействия компонентов во Vue 3

1. Слоты и v-slot: как компоненты становятся гибкими

  • Что такое слот и зачем он нужен

  • Синтаксис v-slot и его короткая запись (#default, #header, #footer)

  • Пример с одним слотом (карточка или layout-компонент)

  • Пример с несколькими слотами:

    <BaseCard>
      <template #header>
        <h3>Заголовок карточки</h3>
      </template>
    
      <template #default>
        <p>Основное содержимое карточки.</p>
      </template>
    
      <template #footer>
        <button>Подробнее</button>
      </template>
    </BaseCard>
    
    <!-- BaseCard.vue -->
    <template>
      <div class="card">
        <header><slot name="header" /></header>
        <main><slot /></main>
        <footer><slot name="footer" /></footer>
      </div>
    </template>
    

    Пояснение: так компоненты становятся универсальными контейнерами, в которые можно "вставить" любую разметку.

2. v-model и defineModel: двусторонняя связь по-новому

  • Как v-model работает под капотом

  • modelValue и @update:modelValue — классический паттерн

  • Vue 3.3+: defineModel() — упрощённая декларация модели в дочернем компоненте

    <!-- ChildInput.vue -->
    <script setup>
    const text = defineModel() // автоматически создаёт prop и emit
    </script>
    
    <template>
      <input v-model="text" placeholder="Введите текст" />
    </template>
    
    <!-- App.vue -->
    <template>
      <ChildInput v-model="username" />
      <p>Вы ввели: {{ username }}</p>
    </template>
    
    <script setup>
    import ChildInput from './ChildInput.vue'
    import { ref } from 'vue'
    
    const username = ref('')
    </script>
    
  • Несколько v-model:

    <!-- ChildComponent.vue -->
    <script setup>
    const first = defineModel('first')
    const last = defineModel('last')
    </script>
    
    <template>
      <input v-model="first" placeholder="Имя" />
      <input v-model="last" placeholder="Фамилия" />
    </template>
    
    <!-- App.vue -->
    <ChildComponent v-model:first="name" v-model:last="surname" />
    

    Пояснение: удобно, когда компонент управляет несколькими значениями (например, фильтрами, диапазонами, формами).

3. Наследование атрибутов: концепция “fall-through”

  • Что такое “fall-through” атрибуты

  • Как Vue автоматически передаёт неиспользованные атрибуты дочернему элементу

  • Пример:

    <BaseButton class="btn-primary" id="save-btn">
      Сохранить
    </BaseButton>
    
    <!-- BaseButton.vue -->
    <template>
      <!-- class и id автоматически "провалятся" сюда -->
      <button><slot /></button>
    </template>
    
  • Как отключить поведение:

    <script setup>
    defineOptions({ inheritAttrs: false })
    </script>
    
    <template>
      <!-- теперь нужно явно передавать $attrs -->
      <button v-bind="$attrs"><slot /></button>
    </template>
    

    Пояснение: “fall-through” делает компоненты гибче, но важно понимать, когда им управлять вручную — например, если нужно фильтровать атрибуты.

💡 Итог

Теперь вы знаете:

  • как строить гибкие компоненты через слоты,

  • как реализовывать двустороннюю связь данных с помощью v-model и defineModel,

  • как управлять атрибутами и их наследованием, избегая неожиданных побочных эффектов.