# Slots
Esta página assume que você já leu o Básico sobre Componentes. Se você não está familizariado com componentes, recomendamos lê-lo primeiro.
# Conteúdo do Slot
Vue implementa uma API de distribuição de conteúdo inspirada no rascunho da especificação de Web Components (opens new window), utilizando o elemento <slot>
como saída de distribuição de conteúdo.
Isto permite a criação de componentes como o seguinte exemplo:
<todo-button>
Adicionar afazer
</todo-button>
2
3
E, no template de <todo-button>
, possuímos:
<!-- template do componente todo-button -->
<button class="btn-primary">
<slot></slot>
</button>
2
3
4
Quando o componente renderizar, <slot></slot>
será substituído por "Adicionar afazer".
<!-- HTML renderizado -->
<button class="btn-primary">
Adicionar afazer
</button>
2
3
4
Strings são apenas um exemplo simples! Slots podem conter código de template também, incluindo HTML:
<todo-button>
<!-- Adiciona um ícone do Font Awesome -->
<i class="fas fa-plus"></i>
Adicionar afazer
</todo-button>
2
3
4
5
Ou, até mesmo, outros componentes:
<todo-button>
<!-- Utilizando um componente para adicionar um ícone -->
<font-awesome-icon name="plus"></font-awesome-icon>
Adicionar afazer
</todo-button>
2
3
4
5
Se o template de <todo-button>
não possuísse o elemento <slot>
, qualquer conteúdo fornecido seria descartado.
<!-- template do componente todo-button, agora sem <slot></slot> -->
<button class="btn-primary">
Criar um novo item
</button>
2
3
4
<todo-button>
<!-- O texto a seguir não será renderizado -->
Adicionar afazer
</todo-button>
2
3
4
# Escopo da Renderização
Quando você desejar utilizar dados dentro de um slot, como em:
<todo-button>
Remover {{ item.name }}
</todo-button>
2
3
Este slot possui acesso às propriedades da mesma instância (isto é, mesmo "escopo"), que o resto do template.
O slot não possui acesso ao escopo de <todo-button>
. Por exemplo, se tentarmos adquirir o valor de action
, não será possível:
<todo-button action="delete">
Ao clicar aqui, este item realizará a ação {{ action }}
<!--
`action` retornará `undefined` neste caso, já que
o conteúdo é apenas transmitido para `<todo-button>`,
ao invés de ser definido como um dado de `<todo-button>`.
-->
</todo-button>
2
3
4
5
6
7
8
Lembre-se, como regra, de que:
Tudo do template pai (parent template) é compilado no escopo pai (parent scope); tudo do template filho (child template), é compilado no escopo filho (child scope).
# Conteúdo Padrão
Há certos casos onde é interessante definir um conteúdo padrão (ou de fallback) para um slot — ou seja, conteúdo que será apenas renderizado caso nenhum outro for informado. Por exemplo, no componente <submit-button>
:
<button type="submit">
<slot></slot>
</button>
2
3
Podemos querer mostrar "Enviar" dentro do <button>
quando nenhum outro texto for informado. Para isto, basta apenas colocar "Enviar" entre as tags <slot>
para defini-lo como o conteúdo padrão.
<button type="submit">
<slot>Enviar</slot>
</button>
2
3
Agora, ao utilizarmos o componente <submit-button>
, sem prover um conteúdo:
<submit-button></submit-button>
"Enviar" será renderizado, por ser o conteúdo padrão:
<button type="submit">
Enviar
</button>
2
3
Entretanto, se especificarmos um conteúdo:
<submit-button>
Salvar
</submit-button>
2
3
O conteúdo informado que será renderizado no lugar de "Enviar":
<button type="submit">
Salvar
</button>
2
3
# Slots Nomeados
Há casos onde é interessante utilizar vários slots. Por exemplo, em um componente <base-layout>
, com o seguinte template:
<div class="container">
<header>
<!-- Queremos nosso conteúdo para o cabeçalho aqui -->
</header>
<main>
<!-- Queremos nosso conteúdo principal aqui -->
</main>
<footer>
<!-- Queremos nosso conteúdo para o rodapé aqui -->
</footer>
</div>
2
3
4
5
6
7
8
9
10
11
Para estes casos, o elemento <slot>
possui um atributo especial, name
, que pode ser utilizado para designar uma identificação única a cada <slot>
e, assim, determinar onde certos conteúdos devem ser renderizados:
<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
Um elemento <slot>
sem o atributo name
, implicitamente, possui name="default"
.
Para fornecer conteúdo a um slot nomeado, precisamos utilizar a diretiva v-slot
em um elemento <template>
, indicando o nome do slot como argumento, logo em seguida:
<base-layout>
<template v-slot:header>
<h1>Aqui podemos ter um título para a página.</h1>
</template>
<template v-slot:default>
<p>Um parágrafo para o conteúdo principal.</p>
<p>E aqui mais um.</p>
</template>
<template v-slot:footer>
<p>Aqui temos informações para contato.</p>
</template>
</base-layout>
2
3
4
5
6
7
8
9
10
11
12
13
14
A partir deste momento, o conteúdo dos elementos <template>
será transferido para os slots correspondentes.
O HTML renderizado será:
<div class="container">
<header>
<h1>Aqui podemos ter um título para a página.</h1>
</header>
<main>
<p>Um parágrafo para o conteúdo principal.</p>
<p>E aqui mais um.</p>
</main>
<footer>
<p>Aqui temos informações para contato.</p>
</footer>
</div>
2
3
4
5
6
7
8
9
10
11
12
É importante destacar que v-slot
pode ser adicionado apenas à um <template>
(exceto neste caso).
# Definição de Escopo em Slots
Em certos momentos, é interessante acessar dados apenas disponíveis no componente filho (child component) no conteúdo de um slot. Um caso comum é utilizarmos um componente para renderizarmos itens de um Array, e queremos o poder de personalizar como cada item é renderizado.
Por exemplo, em um componente que possui uma lista de afazeres:
app.component('todo-list', {
data() {
return {
items: ['Alimentar o gato', 'Comprar leite']
}
},
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
Podemos desejar substituir o {{ item }}
com um <slot>
para customizá-lo no componente pai:
<todo-list>
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
2
3
4
Entretanto, isto não funcionará por estarmos definindo o conteúdo do slot no componente pai, e apenas o componente <todo-list>
possuir acesso ao dado item
.
Para fazer com que item
esteja disponível para uso no conteúdo do slot definido no componente pai, podemos passá-lo como um atributo de <slot>
, através de v-bind
:
<ul>
<li v-for="( item, index ) in items">
<slot :item="item"></slot>
</li>
</ul>
2
3
4
5
Você pode vincular quantos atributos precisar no slot
:
<ul>
<li v-for="( item, index ) in items">
<slot :item="item" :index="index" :another-attribute="anotherAttribute"></slot>
</li>
</ul>
2
3
4
5
Atributos passados para um elemento <slot>
são chamados de props do slot (slot props). Agora, no escopo do componente pai (parent scope), podemos utilizar v-slot
com um valor, que será utilizado, neste caso, como o nome de acesso à estes props do 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
Neste exemplo, escolhemos slotProps
como valor, a fim de tê-lo como o nome de acesso aos props do slot. É possível utilizar qualquer nome que desejar.
# Sintaxe Abreviada para Slot Único e Default
Em casos como o acima, quando apenas o slot padrão (default) é fornecido, podemos utilizar o próprio conteúdo do componente como o template do slot. Deste modo, utilizamos v-slot
diretamente no componente:
<todo-list v-slot:default="slotProps">
<i class="fas fa-check"></i>
<span class="green">{{ slotProps.item }}</span>
</todo-list>
2
3
4
Podemos deixar isto ainda mais simples, já que, assim como não especificar um name
para um elemento <slot>
o faz possuir name="default"
, não especificar um argumento para um v-slot
o faz referenciar default
, implicitamente:
<todo-list v-slot="slotProps">
<i class="fas fa-check"></i>
<span class="green">{{ slotProps.item }}</span>
</todo-list>
2
3
4
É importante mencionar que não é possível utilizar esta sintaxe abreviada para slot padrão em conjunto de slots nomeados, já que isto resultaria em ambiguidade de escopo (scope ambiguity):
<!-- INVÁLIDO, resultará em um aviso -->
<todo-list v-slot="slotProps">
<i class="fas fa-check"></i>
<span class="green">{{ slotProps.item }}</span>
<template v-slot:other="otherSlotProps">
slotProps NÃO está disponível aqui
</template>
</todo-list>
2
3
4
5
6
7
8
9
Sempre que houver mais de um slot, utilize da sintaxe completa, com <template>
, para todos os slots:
<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
# Desestruturando Props do Slot
Internamente, slots com escopo definido encapsulam o conteúdo do seu respectivo slot em uma função com um único argumento:
function (slotProps) {
// ... conteúdo do slot ...
}
2
3
Isto significa que o valor passado à v-slot
pode aceitar, na verdade, qualquer expressão JavaScript válida na definição de argumentos de uma função. Portanto, você também pode utilizar a desestruturação de objetos, da ES2015 (opens new window), a fim de trabalhar apenas com os props que você desejar no conteúdo do slot, como no exemplo abaixo:
<todo-list v-slot="{ item }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
2
3
4
Isto pode tornar o template muito mais limpo, especialmente quando o slot fornece vários props. Isto também abre portas para outras possibilidades, como renomear um prop de nome item
para todo
:
<todo-list v-slot="{ item: todo }">
<i class="fas fa-check"></i>
<span class="green">{{ todo }}</span>
</todo-list>
2
3
4
Você pode até definir valores padrão (fallbacks), a fim de serem utilizados quando um prop do slot não possuir um valor definido (undefined):
<todo-list v-slot="{ item = 'Isto é um afazer placeholder' }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
2
3
4
# Nomes Dinâmicos para Slots
Argumentos dinâmicos de diretiva também funcionam com v-slot
, permitindo a definição de nomes dinâmicos para slots:
<base-layout>
<template v-slot:[dynamicSlotName]>
...
</template>
</base-layout>
2
3
4
5
# Forma Abreviada para Slots Nomeados
Assim como em v-on
e v-bind
, v-slot
também possui uma forma abreviada, basta substituirmos v-slot:
(ou seja, tudo antes do argumento) pelo símbolo especial #
. Portanto, v-slot:header
também pode ser escrito como #header
, por exemplo.
<base-layout>
<template #header>
<h1>Aqui podemos ter um título para a página.</h1>
</template>
<template #default>
<p>Um parágrafo para o conteúdo principal.</p>
<p>E aqui mais um.</p>
</template>
<template #footer>
<p>Aqui temos informações para contato.</p>
</template>
</base-layout>
2
3
4
5
6
7
8
9
10
11
12
13
14
Entretanto, assim como em qualquer outra diretiva, a forma abreviada está apenas disponível quando esta recebe um argumento. Isto significa que o exemplo a seguir possui sintaxe inválida:
<!-- Isto acionará um aviso -->
<todo-list #="{ item }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
2
3
4
5
6
Deste modo, você sempre precisa especificar o nome do slot que você deseja definir ao utilizar a forma abreviada, como em:
<todo-list #default="{ item }">
<i class="fas fa-check"></i>
<span class="green">{{ item }}</span>
</todo-list>
2
3
4