Слоты

Подразумевается, что вы уже изучили и разобрались с разделом Основы компонентов. Если нет — прочитайте его сначала.

Содержимое слотов

Vue реализует API распределения контента, которое основывается на текущем черновике спецификации веб-компонентов, используя элемент <slot> в качестве точек распространения контента.

Это позволяет вам составлять, например, такие компоненты:

<navigation-link url="/profile">
Ваш профиль
</navigation-link>

Затем в шаблоне для <navigation-link> у вас должно быть:

<a
v-bind:href="url"
class="nav-link"
>
<slot></slot>
</a>

Когда компонент будет отрисован, элемент <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>, любой переданный контент будет просто проигнорирован.

Именованные слоты

Бывают случаи, когда полезно иметь несколько слотов. Например, в гипотетическом компоненте 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 на элементе <template> в родителе:

<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>

Определение контента по умолчанию для слота

Бывают случаи, когда полезно предоставить слот с каким-то контентом по умолчанию. Например, для компонента <submit-button> мы можем захотеть, чтобы по умолчанию содержимое кнопки было “Отправить”, но также позволяет пользователям переопределить на “Сохранить”, “Загрузить” или что-либо ещё.

Для достижения этого необходимо указать в шаблоне компонента контент по умолчанию внутри тега <slot>.

<button type="submit">
<slot>Отправить</slot>
</button>

Если родитель предоставит содержимое для слота, он заменит контент по умолчанию.

Область видимости при компиляции

Иногда вы хотите использовать данные внутри слота, например так:

<navigation-link url="/profile">
Привет, {{ user.name }}
</navigation-link>

Слот имеет доступ к тем же свойствам экземпляра (т.е. имеет такую же “область видимости”), что и остальная часть шаблона. Слот не имеет доступа к области видимости <navigation-link>. Например, попытка обратиться к url не сработает. Как правило запомните, что:

Всё в шаблоне родительского компонента компилируется в области видимости родителя; всё в шаблоне потомка — в области видимости потомка.

Слоты с ограниченной областью видимости

Добавлено в версии 2.1.0+

Иногда вам может потребоваться реализовать компонент с переиспользуемым слотом, который может получить доступ к данным дочернего компонента. Например, простой компонент <todo-list> может содержать такой шаблон:

<ul>
<li
v-for="todo in todos"
v-bind:key="todo.id"
>
{{ todo.text }}
</li>
</ul>

Но в некоторых частях нашего приложения мы хотим, чтобы определённые записи todo отображали нечто иное, чем просто todo.text. Здесь и пригодятся слоты с ограниченной областью видимости.

Чтобы реализовать подобное, всё, что нам нужно, это обернуть содержимое todo в элемент <slot>, а затем передать слоту данные, относящиеся к его контексту: в нашем случае объект todo:

<ul>
<li
v-for="todo in todos"
v-bind:key="todo.id"
>
<!-- У нас есть слот на каждый todo, передающий -->
<!-- объект `todo` в качестве входного параметра. -->
<slot v-bind:todo="todo">
<!-- Контент по умолчанию -->
{{ todo.text }}
</slot>
</li>
</ul>

Теперь, когда мы используем компонент <todo-list>, мы можем опционально определить альтернативный <template> для элементов todo, но с доступом к данным элемента через атрибут slot-scope:

<todo-list v-bind:todos="todos">
<!-- Указываем `slotProps` как имя нашей области видимости слота -->
<template slot-scope="slotProps">
<!-- Указываем другой шаблон для элементов, -->
<!-- используя `slotProps` для доступа к данным todo. -->
<span v-if="slotProps.todo.isComplete"></span>
{{ slotProps.todo.text }}
</template>
</todo-list>

С версии 2.5.0+ slot-scope больше не ограничен элементом <template> и может использоваться на любом элементе или компоненте в слоте.

Деструктурирование slot-scope

Значение slot-scope может принимать любое валидное JavaScript-выражение, которое может быть указано в аргументе определения функции. Это означает, что в поддерживаемых окружениях (однофайловых компонентах или современных браузерах) вы также можете использовать деструктурирование ES2015 в выражении, например так:

<todo-list v-bind:todos="todos">
<template slot-scope="{ todo }">
<span v-if="todo.isComplete"></span>
{{ todo.text }}
</template>
</todo-list>

Это отличный способ сделать слоты с ограниченной областью видимости немного чище.