# Слоты
Подразумевается, что уже изучили и разобрались с разделом Основы компонентов. Если нет — прочитайте его сначала.
# Содержимое слота
Vue реализует API распределения контента, вдохновлённое текущим черновиком спецификации веб-компонентов (opens new window), используя элемент <slot>
в качестве точек распространения контента.
Что позволяет создавать например такие компоненты:
<todo-button>
Добавить todo
</todo-button>
2
3
Шаблон <todo-button>
может выглядеть примерно так:
<!-- шаблон компонента todo-button -->
<button class="btn-primary">
<slot></slot>
</button>
2
3
4
При отрисовке компонента <slot></slot>
будет заменён на «Добавить todo».
<!-- отрисованный HTML -->
<button class="btn-primary">
Добавить todo
</button>
2
3
4
И строки — это только начало! Слоты могут содержать код любого шаблона, даже HTML:
<todo-button>
<!-- Добавляем иконку Font Awesome -->
<i class="fas fa-plus"></i>
Добавить todo
</todo-button>
2
3
4
5
Или даже другие компоненты:
<todo-button>
<!-- Используем компонент для добавления иконки -->
<font-awesome-icon name="plus"></font-awesome-icon>
Добавить todo
</todo-button>
2
3
4
5
Если шаблон <todo-button>
не будет содержать элемента <slot>
, то любой переданный контент просто игнорируется.
<!-- В шаблоне компонента todo-button НЕТ <slot> -->
<button class="btn-primary">
Создать новый элемент
</button>
2
3
4
<todo-button>
<!-- Этот текст НЕ БУДЕТ ОТРИСОВАН -->
Добавить todo
</todo-button>
2
3
4
# Область видимости при отрисовке
Если потребуется использовать данные внутри слота, например:
<todo-button>
Удалить {{ item.name }}
</todo-button>
2
3
То в таком слоте будет доступ к тем же свойствам экземпляра (т.е. к той же «области видимости»), как и в остальной части шаблона.
У слота нет доступа к области видимости <todo-button>
. Поэтому попытка обратиться к входному параметру action
не сработает:
<todo-button action="delete">
Клик вызовет действие {{ action }} для элемента
<!--
Значение `action` будет undefined, потому что это содержимое передаётся
ВНУТРЬ <todo-button>, а не определяется СНАРУЖИ компонента <todo-button>.
-->
</todo-button>
2
3
4
5
6
7
Обычно достаточно запомнить что:
Всё в родительском шаблоне компилируется в области видимости родительского компонента; всё в шаблоне дочернего компилируется в области видимости дочернего компонента.
# Содержимое слота по умолчанию
Часто полезно указать содержимое слота по умолчанию, которое будет использоваться только когда ничего не передаётся в слот. Например, для компонента <submit-button>
:
<button type="submit">
<slot></slot>
</button>
2
3
Удобнее указать текст по умолчанию «Отправить», который будет отображаться большую часть времени. Для этого нужно сделать «Отправить» содержимым по умолчанию, поместив его между тегами <slot>
:
<button type="submit">
<slot>Отправить</slot>
</button>
2
3
Теперь, используя <submit-button>
в родительском компоненте и не указывая содержимое для слота:
<submit-button></submit-button>
будет отображаться содержимое по умолчанию — «Отправить»:
<button type="submit">
Отправить
</button>
2
3
Но стоит определить его:
<submit-button>
Сохранить
</submit-button>
2
3
и оно будет использовано для отображения:
<button type="submit">
Сохранить
</button>
2
3
# Именованные слоты
Зачастую удобно иметь несколько слотов. К примеру, для компонента <base-layout>
со следующим шаблоном:
<div class="container">
<header>
<!-- Здесь должен быть заголовок -->
</header>
<main>
<!-- Здесь основной контент -->
</main>
<footer>
<!-- Здесь контент подвала -->
</footer>
</div>
2
3
4
5
6
7
8
9
10
11
В таких случаях элементу <slot>
можно указать специальный атрибут name
, который будет использован для присвоения уникального ID различным слотам, чтобы определить где какое содержимое необходимо отобразить:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
2
3
4
5
6
7
8
9
10
11
Элемент <slot>
без name
неявно получает имя «default».
Для объявления содержимого для именованного слота, необходимо воспользоваться директивой v-slot
на элементе <template>
, передав имя слота аргументом v-slot
:
<base-layout>
<template v-slot:header>
<h1>Здесь мог быть заголовок страницы</h1>
</template>
<template v-slot:default>
<p>Параграф для основного контента.</p>
<p>И ещё один.</p>
</template>
<template v-slot:footer>
<p>Некая контактная информация</p>
</template>
</base-layout>
2
3
4
5
6
7
8
9
10
11
12
13
14
Теперь содержимое элементов <template>
будет передаваться в соответствующие слоты.
Отрисованный HTML получится таким:
<div class="container">
<header>
<h1>Здесь мог быть заголовок страницы</h1>
</header>
<main>
<p>Параграф для основного контента.</p>
<p>И ещё один.</p>
</main>
<footer>
<p>Некая контактная информация</p>
</footer>
</div>
2
3
4
5
6
7
8
9
10
11
12
Запомните, v-slot
можно добавлять только на теги <template>
(с одним исключением).
# Слоты с ограниченной областью видимости
Иногда в содержимом слота может потребоваться доступ к данным, доступным только в дочернем компоненте. Частый случай подобного — когда в компоненте отображается массив элементов, и нужна возможность управлять отрисовкой каждого элемента.
Например, есть компонент со списком дел:
app.component('todo-list', {
data() {
return {
items: ['Покормить кота', 'Купить молока']
}
},
template: `
<ul>
<li v-for="(item, index) in items">
{{ item }}
</li>
</ul>
`
})
2
3
4
5
6
7
8
9
10
11
12
13
14
Заменим {{ item }}
на <slot>
для управления отрисовкой в родительском компоненте:
<todo-list>
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
2
3
4
К сожалению так это не сработает, потому что только у компонента <todo-list>
есть доступ к item
, а в этом примере содержимое слота указывается в родительском.
Чтобы в родительском компоненте предоставить доступ к item
для содержимого слота, необходимо добавить элемент <slot>
и привязать требуемые данные атрибутом:
<ul>
<li v-for="(item, index) in items">
<slot :item="item"></slot>
</li>
</ul>
2
3
4
5
Можно привязывать к slot
любое количество атрибутов:
<ul>
<li v-for="(item, index) in items">
<slot
:item="item"
:index="index"
:another-attribute="anotherAttribute"
></slot>
</li>
</ul>
2
3
4
5
6
7
8
9
Атрибуты, привязанные к элементу <slot>
, называются входными параметрами слота. Теперь, в родительской области видимости, можно использовать v-slot
со значением, чтобы определить имя переменной с входными параметрами, привязанными к слоту:
<todo-list>
<template v-slot:default="slotProps">
<i class="fas fa-check"></i>
<span class="green">{{ slotProps.item }}</span>
</template>
</todo-list>
2
3
4
5
6
В этом примере объект со всеми входными параметрами слота будет с именем slotProps
, но можно использовать и любое другое, которое нравится.
# Сокращённый синтаксис для единственного слота по умолчанию
Если указывается содержимое только для слота по умолчанию, то можно использовать тег компонента в качестве шаблона слота и можно указывать v-slot
сразу на компоненте:
<todo-list v-slot:default="slotProps">
<i class="fas fa-check"></i>
<span class="green">{{ slotProps.item }}</span>
</todo-list>
2
3
4
Такую запись можно сократить ещё больше. Предполагается, что содержимое относится к слоту по умолчанию, если иного не указано явно, поэтому v-slot
без аргумента означает слот по умолчанию:
<todo-list v-slot="slotProps">
<i class="fas fa-check"></i>
<span class="green">{{ slotProps.item }}</span>
</todo-list>
2
3
4
Обратите внимание, что подобный сокращённый синтаксис для слота по умолчанию нельзя смешивать с именованными слотами, потому что это приводит к неоднозначности области видимости:
<!-- НЕПРАВИЛЬНО, будет выбрасываться предупреждение -->
<todo-list v-slot="slotProps">
<i class="fas fa-check"></i>
<span class="green">{{ slotProps.item }}</span>
<template v-slot:other="otherSlotProps">
slotProps здесь НЕДОСТУПНЫ
</template>
</todo-list>
2
3
4
5
6
7
8
9
При наличии нескольких слотов всегда используйте полный синтаксис с объявлением элементов <template>
для всех слотов:
<todo-list>
<template v-slot:default="slotProps">
<i class="fas fa-check"></i>
<span class="green">{{ slotProps.item }}</span>
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</todo-list>
2
3
4
5
6
7
8
9
10
# Деструктуризация входных параметров слота
Под капотом, слоты с ограниченной областью видимости оборачивают своё содержимое слота в функцию, которая аргументом принимает входные параметры:
function(slotProps) {
// ... содержимое слота ...
}
2
3
Поэтому значение v-slot
может быть любым допустимым выражением JavaScript, которое допустимо использовать на позиции аргумента определения функции. Например можно применять деструктурирование ES2015 (opens new window), чтобы получать определённые входные параметры слота:
<todo-list v-slot="{ item }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
2
3
4
Такой подход делает шаблон намного чище, особенно если у слота множество входных параметров. Это открывает и другие возможности, например, переименование свойства item
в todo
используемого входного параметра:
<todo-list v-slot="{ item: todo }">
<i class="fas fa-check"></i>
<span class="green">{{ todo }}</span>
</todo-list>
2
3
4
Кроме того, можно определить значение по умолчанию, которое будет использоваться если входной параметр для слота не был определён:
<todo-list v-slot="{ item = 'Нет информации' }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
2
3
4
# Динамическое имя слота
Динамические аргументы директивы работают и с v-slot
, что позволяет установить динамическое имя слота:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
2
3
4
5
# Сокращённая запись именованных слотов
Кроме v-on
и v-bind
, есть сокращённая запись и у v-slot
, которая заменяет всё перед аргументом (v-slot:
) на символ #
, а v-slot:header
можно сократить до #header
:
<base-layout>
<template #header>
<h1>Здесь мог быть заголовок страницы</h1>
</template>
<template #default>
<p>Параграф для основного контента.</p>
<p>И ещё один.</p>
</template>
<template #footer>
<p>Некая контактная информация</p>
</template>
</base-layout>
2
3
4
5
6
7
8
9
10
11
12
13
14
Однако, как и с другими директивами, сокращение можно использовать только при наличии аргумента и следующий синтаксис будет неправильным:
<!-- НЕ ЗАРАБОТАЕТ и выкинет предупреждение -->
<todo-list #="{ item }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
2
3
4
5
Необходимо всегда указывать имя слота, чтобы использовать сокращённую запись:
<todo-list #default="{ item }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
2
3
4