Як додати резервний контент для слотів у Vue?
Fallback content для слота - це шаблон всередині тегу <slot>, який відображається коли батьківський компонент не передає власний контент для цього слота.
Теорія
TL;DR
- Уявіть поштову скриньку: fallback - це табличка "Пошти немає", яка з'являється коли нічого не доставили
- Будь-який контент всередині
<slot>стає резервним за замовчуванням - Контент від батьківського компонента повністю замінює fallback. Вони не об'єднуються
- Працює з default та іменованими слотами однаково
- Підходить для опціональних секцій UI: порожні стани, стандартні заголовки, загальні повідомлення
Швидкий приклад
<!-- Button.vue -->
<template>
<button class="btn">
<slot>Натисни мене</slot> <!-- резервний текст -->
</button>
</template>
<!-- Без контенту: відображає "Натисни мене" -->
<Button />
<!-- З контентом: відображає "Зберегти" -->
<Button>Зберегти</Button>Дочірній компонент визначає що показати, якщо батько нічого не передав. Батько може перевизначити це, але не зобов'язаний.
Як Vue вирішує що рендерити
Vue перевіряє чи передав батьківський компонент контент для конкретного слота. Якщо так - рендерить його. Якщо ні - використовує внутрішній шаблон слота. Жодного змішування: одне перемагає, інше зникає повністю.
Це відбувається під час монтування компонента. Компілятор шаблонів генерує умовну перевірку: якщо slots.default існує (або slots.header для іменованих слотів) - рендерить його, інакше рендерить fallback-елементи. Жодних додаткових витрат під час виконання, крім звичайного diffing vnode.
Коли використовувати
- Опціональні секції макету (бічні панелі, заголовки) де більшість сторінок передає контент, але деякі ні
- Компоненти сповіщень і алертів де загальне повідомлення покриває типовий випадок
- Обгортки для кнопок і полів введення з розумними мітками за замовчуванням
- Не використовуй fallback для слотів, які завжди заповнюються з батька. Для обов'язкового контенту краще props
Більшість дашбордів, з якими я працював, використовують fallback іменованих слотів саме так: компонент макету оголошує безпечні значення за замовчуванням, а окремі сторінки перевизначають тільки те що потрібно.
Типові помилки
Очікування що fallback зіллється з контентом батька
<!-- Неправильне очікування -->
<ChildComponent>
<p>Додатковий абзац</p>
</ChildComponent>
<!-- Показується тільки "Додатковий абзац". Fallback зник. -->Слоти замінюють повністю. Якщо потрібно і резервний контент і додатковий - визнач окремі іменовані слоти.
Неправильна назва іменованого слота
<!-- Дочірній оголошує: <slot name="header"> -->
<!-- Батько використовує неправильну назву -->
<template #head>Кастомний заголовок</template>
<!-- "head" ніколи не збігається з "header". Fallback завжди показується. -->Назви чутливі до регістру і мають збігатися точно. Default-слот назви не має.
Порожній template обманює перевірку fallback
<ChildComponent>
<template #default></template>
</ChildComponent>
<!-- Vue бачить що слот передано. Fallback не відображається. -->Vue вважає будь-який переданий слот заповненим, навіть порожній. Це ловить тих, хто намагається умовно передавати слоти з батьківського компонента.
Динамічні fallback і SSR
<!-- Ризиковано в Nuxt -->
<slot><div v-if="isClient">Fallback</div></slot>Якщо fallback залежить від стану тільки на клієнті, виникне помилка гідратації: сервер нічого не рендерить, клієнт додає div. Тримай fallback статичними або обгортай в <ClientOnly>.
Де використовується
- Vuetify
v-card: fallback для заголовка і блоку дій коли батько їх не передає - Element Plus
el-table: повідомлення про порожній стан через fallback слота - Quasar
q-page: стандартні заголовки і бічні панелі з брендуванням додатка - Nuxt UI: fallback-сповіщення в слотах глобального макету
Питання на співбесіді
Q: Чи може fallback контент звертатись до даних дочірнього компонента?
A: Так. Fallback живе в scope шаблону дочірнього компонента, тому має повний доступ до його реактивних даних, обчислюваних властивостей і методів.
Q: Що станеться якщо батько передасть контент для слота якого немає в дочірньому?
A: Vue його ігнорує. Дочірній компонент має явно оголосити <slot name="foo">, інакше контент зникне без жодного попередження.
Q: Чи працюють scoped slots з fallback?
A: Так, але є нюанс. Fallback всередині scoped slot може звертатись до даних що слот передає, бо виконується в scope дочірнього компонента. Але якщо батько перевизначає слот, він має використати v-slot="{ items }" щоб отримати ці дані. Fallback в такому разі взагалі не виконується.
Q: Чи є різниця в продуктивності між fallback і звичайним контентом?
A: Жодної суттєвої. Перевірка - це умовна логіка на етапі компіляції. Обидва шляхи генерують однакову кількість vnode-операцій.
Приклади
Кнопка з міткою за замовчуванням
<!-- SubmitButton.vue -->
<template>
<button type="submit" class="btn-primary">
<slot>Відправити</slot>
</button>
</template>
<!-- Використання -->
<SubmitButton /> <!-- Відображає: Відправити -->
<SubmitButton>Зберегти чернетку</SubmitButton> <!-- Відображає: Зберегти чернетку -->
<SubmitButton>Видалити</SubmitButton> <!-- Відображає: Видалити -->Один компонент, різні мітки, жодних додаткових props. Fallback покриває типовий випадок, а там де потрібно щось інше - перевизначають при виклику.
Макет дашборду з іменованими слотами
<!-- AppLayout.vue -->
<template>
<div class="layout">
<header>
<slot name="header">
<h1>Мій Додаток</h1> <!-- загальний fallback -->
</slot>
</header>
<aside>
<slot name="sidebar">
<p>Бічна панель не налаштована</p>
</slot>
</aside>
<main>
<slot>Завантажте свій контент тут</slot>
</main>
</div>
</template>
<!-- CRM-сторінка: перевизначає header і sidebar, залишає main як fallback -->
<AppLayout>
<template #header>
<h1>Дашборд клієнтів</h1>
</template>
<template #sidebar>
<nav>Користувачі | Звіти | Оплата</nav>
</template>
<!-- #default не передано: main показує резервний текст -->
</AppLayout>Кожен слот незалежний. Передача контенту для #header не впливає на #sidebar чи default-слот. Fallback заповнює тільки ті прогалини, які батько лишив відкритими.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.