Подразумевается, что вы уже изучили и разобрались с разделом Основы компонентов. Если нет — прочитайте его сначала.
В версии 2.6.0 был представлен новый единый синтаксис (директива
v-slot
) для именованных слотов и слотов с ограниченной областью видимости. Он заменяет атрибутыslot
иslot-scope
, которые в настоящий момент объявлены устаревшими, но не удалены и документированы здесь. Обоснование введения нового синтаксиса описано в этом RFC.
Vue реализует API распределения содержимого, вдохновлённое текущим черновиком спецификации веб-компонентов, используя элемент <slot>
в качестве точек распространения содержимого.
Например, это позволит составлять такие компоненты:
<navigation-link url="/profile">
Ваш профиль
</navigation-link>
Для этого шаблон <navigation-link>
должен быть например таким:
<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>
При отрисовке компонента <slot></slot>
будет заменён на «Ваш профиль». Слоты могут содержать любой код шаблона, в том числе HTML:
<navigation-link url="/profile">
<!-- Добавляем иконку из набора Font Awesome -->
<span class="fa fa-user"></span>
Ваш профиль
</navigation-link>
Или даже другие компоненты:
<navigation-link url="/profile">
<!-- Используем компонент для добавления иконки -->
<font-awesome-icon name="user"></font-awesome-icon>
Ваш профиль
</navigation-link>
Если шаблон <navigation-link>
не содержит элемент <slot>
, любой переданное содержимое будет просто проигнорировано.
Если необходимо использовать данные внутри слота, например:
<navigation-link url="/profile">
Вы вошли как {{ user.name }}
</navigation-link>
То этот слот имеет доступ к тем же свойствам экземпляра (т.е. к той же «области видимости»), что и остальная часть шаблона. Слот не имеет доступа к области видимости <navigation-link>
. Поэтому попытка получить url
не сработает:
<navigation-link url="/profile">
Щелкните для перехода сюда: {{ url }}
<!--
Значение `url` будет неопределено (undefined), потому что это
содержимое передаётся _на_ <navigation-link>, а не определяется
_внутри_ компонента <navigation-link>.
-->
</navigation-link>
Как правило, достаточно запомнить что:
Всё в родительском шаблоне компилируется в области видимости родительского компонента; всё в дочернем шаблоне компилируется в области видимости дочернего компонента.
Бывает полезным указать запасное содержимое слота (т.е. по умолчанию), которое будет отображаться только тогда, когда ничего не передавалось в слот. Например, в компоненте <submit-button>
:
<button type="submit">
<slot></slot>
</button>
Было бы удобно если текст «Отправить» отображался внутри <button>
большую часть времени. Чтобы сделать «Отправить» в качестве содержимого по умолчанию, необходимо поместить его между тегами <slot>
:
<button type="submit">
<slot>Отправить</slot>
</button>
Теперь, при использовании <submit-button>
в родительском компоненте и не указывая содержимое для слота:
<submit-button></submit-button>
отобразится содержимое по умолчанию — «Отправить»:
<button type="submit">
Отправить
</button>
Но если указать содержимое:
<submit-button>
Сохранить
</submit-button>
Тогда оно будет использовано для отображения:
<button type="submit">
Сохранить
</button>
Обновлено в версии 2.6.0+. Устаревший синтаксис с использованием атрибута
slot
можно посмотреть здесь.
Зачастую удобно иметь несколько слотов. К примеру, для компонента <base-layout>
со следующим шаблоном:
<div class="container">
<header>
<!-- Мы хотим отобразить содержимое заголовка здесь -->
</header>
<main>
<!-- Мы хотим отобразить основное содержимое здесь -->
</main>
<footer>
<!-- Мы хотим отобразить содержимое подвала здесь -->
</footer>
</div>
В таких случаях элементу <slot>
можно указать специальный атрибут name
, который используется для определения дополнительных слотов:
<div class="container">
<header>
<slot name="header"></slot>
</header>
<main>
<slot></slot>
</main>
<footer>
<slot name="footer"></slot>
</footer>
</div>
Обычный <slot>
без name
неявно имеет имя «default».
Чтобы указать содержимое для именованного слота, нужно использовать директиву v-slot
на <template>
, передав имя слота аргументом v-slot
:
<base-layout>
<template v-slot:header>
<h1>Здесь мог быть заголовок страницы</h1>
</template>
<p>Параграф для основного содержимого.</p>
<p>И ещё один.</p>
<template v-slot:footer>
<p>Некая контактная информация</p>
</template>
</base-layout>
Теперь всё внутри элементов <template>
будет передаваться в соответствующие слоты. Предполагается, что любое содержимое, не обёрнутое в <template>
с использованием v-slot
, предназначается для слота по умолчанию.
Однако, можно и явно обернуть в <template>
содержимое слота по умолчанию:
<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>
В обоих случаях, итоговый HTML будет таким:
<div class="container">
<header>
<h1>Здесь мог быть заголовок страницы</h1>
</header>
<main>
<p>Параграф для основного содержимого</p>
<p>И ещё один.</p>
</main>
<footer>
<p>Некая контактная информация</p>
</footer>
</div>
Обратите внимание, что v-slot
можно добавлять только на <template>
(за одним исключением), в отличие от устаревшего атрибута slot
.
Обновлено в версии 2.6.0+. Устаревший синтаксис, с использованием атрибута
slot-scope
можно посмотреть здесь.
Иногда для содержимого слота полезно иметь возможность использовать данные, доступные только в дочернем компоненте. Например, представьте компонент <current-user>
со следующим шаблоном:
<span>
<slot>{{ user.lastName }}</slot>
</span>
Может потребоваться заменить это содержимое по умолчанию, например, чтобы отобразить имя пользователя, а не фамилию:
<current-user>
{{ user.firstName }}
</current-user>
Однако это не сработает, потому что только компонент <current-user>
имеет доступ к user
, а новое содержимое слота отрисовывается в родительском.
Чтобы сделать user
доступным для содержимого слота в родительском компоненте, необходимо добавить привязку user
в качестве атрибута на элементе <slot>
:
<span>
<slot v-bind:user="user">
{{ user.lastName }}
</slot>
</span>
Атрибуты, привязанные к элементу <slot>
, называются входными параметрами слота. Теперь, в родительской области видимости, можно использовать v-slot
со значением, чтобы указать имя для предоставленных слоту входных параметров:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
</current-user>
В этом примере мы выбрали имя объекта slotProps
, содержащего все входные параметры слота, но можно использовать любое другое, которое нравится.
В случаях, когда только слоту по умолчанию предоставляется содержимое, тег компонента можно использовать в качестве шаблона слота. Это позволяет использовать v-slot
непосредственно на компоненте:
<current-user v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</current-user>
Эту запись можно сократить ещё больше. Предполагается, что неуказанное явно содержимое относится к слоту по умолчанию, так и v-slot
без аргумента означает слот по умолчанию:
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
</current-user>
Обратите внимание, что такой сокращённый синтаксис для слота по умолчанию нельзя смешивать с именованными слотами, потому что это приведёт к неоднозначности области видимости:
<!-- НЕПРАВИЛЬНО, будет выкидывать предупреждение -->
<current-user v-slot="slotProps">
{{ slotProps.user.firstName }}
<template v-slot:other="otherSlotProps">
slotProps НЕДОСТУПНЫ здесь
</template>
</current-user>
При наличии нескольких слотов лучше используйте полный синтаксис на основе <template>
для всех слотов:
<current-user>
<template v-slot:default="slotProps">
{{ slotProps.user.firstName }}
</template>
<template v-slot:other="otherSlotProps">
...
</template>
</current-user>
Слоты с ограниченной областью видимости, под капотом работают заключая содержимое слота в функцию, и передавая входные параметры одним аргументом:
function (slotProps) {
// ... содержимое слота ...
}
Это значит, что значение v-slot
может принимать любое допустимое выражение JavaScript, которое может появиться в позиции аргумента определения функции. Поэтому в поддерживаемых окружениях (однофайловых компонентах или современных обозревателях), можно также использовать деструктурирование ES2015 чтобы извлекать определённые входные параметры слотов, например вот так:
<current-user v-slot="{ user }">
{{ user.firstName }}
</current-user>
Такой подход сделает шаблон намного чище, особенно когда слот предоставляет множество входных параметров. Это также открывает другие возможности, такие как переименование входных параметров, например user
в person
:
<current-user v-slot="{ user: person }">
{{ person.firstName }}
</current-user>
Можно даже определять значения по умолчанию, которые будут использоваться в случае, если входной параметр слота не определён:
<current-user v-slot="{ user = { firstName: 'Гость' } }">
{{ user.firstName }}
</current-user>
Добавлено в версии 2.6.0+
Динамические аргументы директивы также работают с v-slot
, что позволяет указать динамическое имя слота:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
Добавлено в версии 2.6.0+
Аналогично v-on
и v-bind
, у v-slot
есть собственное сокращение, заменяющее всё перед аргументом (v-slot:
) специальным символом #
. Например, v-slot:header
можно записать как #header
:
<base-layout>
<template #header>
<h1>Здесь мог быть заголовок страницы</h1>
</template>
<p>Параграф для основного содержимого.</p>
<p>И ещё один.</p>
<template #footer>
<p>Некая контактная информация</p>
</template>
</base-layout>
Однако, как и в случае с другими директивами, сокращение доступно только при наличии аргумента. Это означает, что следующий синтаксис недопустим:
<!-- Это выкинет предупреждение -->
<current-user #="{ user }">
{{ user.firstName }}
</current-user>
Необходимо всегда указывать имя слота, если хотите использовать сокращение:
<current-user #default="{ user }">
{{ user.firstName }}
</current-user>
Входные параметры слотов позволяют превратить их в переиспользуемые шаблоны, которые могут отображать различное содержимое, основываясь на входных параметрах. Это очень полезно при разработке переиспользуемых компонентов, которые инкапсулируют логику данных, позволяя родительскому компоненту настраивать часть своего шаблона.
Например, реализуем компонент <todo-list>
, который содержит шаблон и логику фильтрации для списка задач:
<ul>
<li
v-for="todo in filteredTodos"
v-bind:key="todo.id"
>
{{ todo.text }}
</li>
</ul>
Вместо жёсткого кодирования содержимого каждой задачи списка, мы можем позволить родительскому компоненту взять на себя управление отображением с помощью слота, а затем привязать todo
в качестве входного параметра слота:
<ul>
<li
v-for="todo in filteredTodos"
v-bind:key="todo.id"
>
<!--
Указываем слот для каждой задачи, передавая
объект `todo` в качестве входного параметра.
-->
<slot name="todo" v-bind:todo="todo">
<!-- Содержимое по умолчанию -->
{{ todo.text }}
</slot>
</li>
</ul>
Теперь, при использовании <todo-list>
, можно вариативно переопределить <template>
для элементов списка, но сохранив доступ к данным из дочернего компонента:
<todo-list v-bind:todos="todos">
<template v-slot:todo="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>
Однако, это едва ли не вершина айсберга возможностей на которые способны слоты с ограниченной областью видимости. Несколько реальных примеров использования слотов с ограниченной областью видимости можно посмотреть в библиотеках Vue Virtual Scroller, Vue Promised, и Portal Vue.
В версии Vue 2.6.0 была представлена директива
v-slot
, предлагающая улучшенный альтернативный API для всё ещё поддерживаемых атрибутовslot
иslot-scope
. Полное обоснование добавленияv-slot
описано в RFC. Атрибутыslot
иslot-scope
будут поддерживаться в новых версиях 2.x, но официально они считаются устаревшими и будут удалены во Vue 3.
slot
Устаревшее с 2.6.0+. Новый рекомендованный синтаксис можно посмотреть здесь.
Для передачи содержимого в именованные слоты из родительского компонента используйте специальный атрибут slot
на теге <template>
(в качестве примера здесь используется компонент <base-layout>
, описанный выше):
<base-layout>
<template slot="header">
<h1>Здесь мог быть заголовок страницы</h1>
</template>
<p>Параграф для основного содержимого.</p>
<p>И ещё один.</p>
<template slot="footer">
<p>Некая контактная информация</p>
</template>
</base-layout>
Также атрибут slot
можно использовать непосредственно на обычном элементе:
<base-layout>
<h1 slot="header">Здесь мог быть заголовок страницы</h1>
<p>Параграф для основного содержимого.</p>
<p>И ещё один.</p>
<p slot="footer">Некая контактная информация</p>
</base-layout>
Может быть только один слот без имени, который является слотом по умолчанию и служит для отображения оставшегося содержимого. В обоих случаях итоговый HTML будет таким:
<div class="container">
<header>
<h1>Здесь мог быть заголовок страницы</h1>
</header>
<main>
<p>Параграф для основного содержимого.</p>
<p>И ещё один.</p>
</main>
<footer>
<p>Некая контактная информация</p>
</footer>
</div>
slot-scope
Устаревшее с 2.6.0+. Новый рекомендованный синтаксис можно посмотреть здесь.
Для получения входных параметров, переданных в слот, родительский компонент может использовать <template>
с атрибутом slot-scope
(в качестве примера используется <slot-example>
, описанный выше):
<slot-example>
<template slot="default" slot-scope="slotProps">
{{ slotProps.msg }}
</template>
</slot-example>
Здесь slot-scope
объявляет объект с полученными входными параметрами как переменную slotProps
, и делает его доступным внутри области видимости <template>
. Можно назвать slotProps
как угодно, придерживаясь именования аргументов для функций в JavaScript.
В примере slot="default"
можно опустить, так как это подразумевается:
<slot-example>
<template slot-scope="slotProps">
{{ slotProps.msg }}
</template>
</slot-example>
Атрибут slot-scope
также можно использовать непосредственно не только на элементах <template>
(включая компоненты):
<slot-example>
<span slot-scope="slotProps">
{{ slotProps.msg }}
</span>
</slot-example>
Значение slot-scope
может принимать любое допустимое выражение JavaScript, которое может использоваться в позиции аргумента определения функции. Это означает, что в поддерживаемых средах (однофайловых компонентах или современных обозревателях) также можно использовать деструктурирование ES2015 в выражении, например так:
<slot-example>
<span slot-scope="{ msg }">
{{ msg }}
</span>
</slot-example>
Используя <todo-list>
описанный выше в качестве примера, вот равнозначная запись с использованием slot-scope
:
<todo-list v-bind:todos="todos">
<template slot="todo" slot-scope="{ todo }">
<span v-if="todo.isComplete">✓</span>
{{ todo.text }}
</template>
</todo-list>