Slots

Esta página assume que você já leu o Básico sobre Componentes. Leia lá primeiro se você for novo com componentes.

Na versão 2.6, Nós introduzimos uma nova sintaxe unificada (A diretiva v-slot) para os slots nomeados e com escopo. Ele substitui os atributos slot e slot-scope, os quais estão agora obsoletos, mas não foram removidos e ainda estão documentados aqui. Os fundamentos para a nova sintaxe estão descritos neste RFC.

Conteúdo do Slot

Vue implementa uma API de distribuição de conteúdo que é modelada após o atual detalhamento da especificação dos componentes da Web, usando o elemento <slot> para servir como saída de distribuição de conteúdos.

Isso permite que você componha componentes como esse:

<navigation-link url="/profile">
  Seu Perfil
</navigation-link>

Então no template para <navigation-link>, você poderá ter:

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

Quando o componente renderizar, <slot></slot> será substituido por “Seu Perfil”. Slots podem conter qualquer tipo de código template, incluindo HTML:

<navigation-link url="/profile">
  <!-- Adicionando ícones Font Awesome -->
  <span class="fa fa-user"></span>
  Seu Perfil
</navigation-link>

Ou até mesmo outros componentes:

<navigation-link url="/profile">
  <!-- Usando um componente para adicionar um ícone -->
  <font-awesome-icon name="user"></font-awesome-icon>
  Seu Perfil
</navigation-link>

Se no <navigation-link> não contém um elemento <slot>, qualquer conteúdo passado para ele simplesmente será descartado.

Escopo de compilação

Quando você quer usar um dado dentro de um slot, como em:

<navigation-link url="/profile">
  Logado como {{ user.name }}
</navigation-link>

Esse slot tem acesso as mesmas propriedades da instância (isto é, ao mesmo “escopo”) como o resto do template. O slot não tem acesso ao escopo do <navigation-link>. Por exemplo, tentando acessar a url não funcionaria:

<navigation-link url="/profile">
  Clicando aqui te enviará para: {{ url }}
  <!--
  O `url` será undefined, porque esse conteúdo é passado
  para <navigation-link>, em vez de definido dentro do componente <navigation-link>.
  -->
</navigation-link>

Como regra, lembre-se de que:

Tudo no template pai é compilado no escopo do pai; Tudo no template filho é compilado no escopo do filho.

Conteúdo de Fallback

Há casos em que é útil especificar o conteúdo de fallback (isto é, padrão) para um slot, a ser renderizado somente quando nenhum conteúdo é fornecido. Por exemplo, no componente <submit-button>:

<button type="submit">
  <slot></slot>
</button>

Podemos querer que o texto “Enviar” seja renderizado dentro do <button> na maioria das vezes. para “Enviar” o conteúdo de fallback, podemos colocá-lo entre as tags <slot>:

<button type="submit">
  <slot>Enviar</slot>
</button>

Agora, quando usamos <submit-button> no componente pai, sem fornecer conteúdo para o slot:

<submit-button></submit-button>

Irá renderizar o conteúdo de fallback, “Enviar”:

<button type="submit">
  Enviar
</button>

Mas se nós fornecermos conteúdo:

<submit-button>
  Salvar
</submit-button>

Então, o conteúdo fornecido será renderizado:

<button type="submit">
  Salvar
</button>

Slots Nomeados

Atualizado em 2.6.0+. Veja aqui para a sintaxe obsoleta usando o atributo slot.

Há momentos em que é útil ter múltiplos elementos slots. Por exemplo, em um componente <base-layout> com o seguinte template:

<div class="container">
  <header>
    <!-- Nós queremos o cabeçalho aqui -->
  </header>
  <main>
    <!-- Nós queremos o conteúdo principal aqui -->
  </main>
  <footer>
    <!-- Nós queremos o rodapé aqui -->
  </footer>
</div>

Para estes casos, o elemento <slot> tem um atributo especial chamado, name, que pode ser usado para definir slots adicionais:

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

Uma saída <slot> sem name tem implicitamente o nome “default”.

Para fornecer conteúdo para slots nomeados, nós podemos usar a diretiva v-slot em um <template>, fornecendo o nome do slot como argumento do v-slot:

<base-layout>
  <template v-slot:header>
    <h1>Aqui pode estar um título da página</h1>
  </template>

  <p>Um parágrafo para o conteúdo principal.</p>
  <p>E um outro parágrafo.</p>

  <template v-slot:footer>
    <p>Aqui estão algumas informações de contato</p>
  </template>
</base-layout>

Agora tudo dentro dos elementos <template> serão passados aos slots correspondentes. Qualquer conteúdo não envolvido por um <template> usando v-slot é assumido como sendo o slot default.

No entanto, você ainda pode incluir o conteúdo do slot default em um <template> se você deseja ser explícito:

<base-layout>
  <template v-slot:header>
    <h1>Aqui pode estar um título da página</h1>
  </template>

  <template v-slot:default>
    <p>Um parágrafo para o conteúdo principal.</p>
    <p>E um outro parágrafo.</p>
  </template>

  <template v-slot:footer>
    <p>Aqui estão algumas informações de contato</p>
  </template>
</base-layout>

De qualquer forma, O HTML renderizado será:

<div class="container">
  <header>
    <h1>Aqui pode estar um título da página</h1>
  </header>
  <main>
    <p>Um parágrafo para o conteúdo principal.</p>
    <p>E um outro parágrafo.</p>
  </main>
  <footer>
    <p>Aqui estão algumas informações de contato</p>
  </footer>
</div>

Observe que v-slot só pode ser adicionado a um <template> (com única exceção), ao contrário dos atributos slot.

Slots com Escopo Definido

Atualizado em 2.6.0+. Veja aqui para a sintaxe obsoleta usando o atributo slot-scope.

As vezes, é útil que o conteúdo do slot tenha acesso aos dados disponíveis apenas no componente filho. Por exemplo, imagine um componente <current-user> com o seguinte template:

<span>
  <slot>{{ user.lastName }}</slot>
</span>

Podemos querer substituir esse conteúdo de fallback para exibir o primeiro nome do usuário, em vez do último, assim:

<current-user>
  {{ user.firstName }}
</current-user>

Isso não funcionará, no entanto, porque somente o componente <current-user> tem acesso ao user e o conteúdo que estamos fornecendo é renderizado no pai.

Para tornar user disponível para o conteúdo do slot no pai, podemos vincular user como um atributo do elemento <slot>:

<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>

Atributos vinculados a um elemento <slot> são chamados de props do slot. Agora, no escopo pai, podemos usar o v-slot com um valor para definir um nome para as propriedades slot que fornecemos:

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>
</current-user>

Neste exemplo, escolhemos nomear o objeto que contém todos os nossos props do slot slotProps, mas você pode usar qualquer nome que desejar.

Sintaxe abreviada para slots default

Em casos como acima, quando apenas o slot default é fornecido, as tags do componente podem ser usadas como template. Isso nos permite usar o v-slot diretamente no componente:

<current-user v-slot:default="slotProps">
  {{ slotProps.user.firstName }}
</current-user>

Isso pode ser encurtado ainda mais. Assim como o conteúdo não especificado é considerado como sendo o slot default, assume-se que o v-slot sem um argumento se refira ao slot default:

<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
</current-user>

Observe que a sintaxe abreviada para o slot default não pode ser misturada com slots nomeados, pois isso levaria a ambiguidade do escopo:

<!-- INVÁLIDO, resultará em aviso -->
<current-user v-slot="slotProps">
  {{ slotProps.user.firstName }}
  <template v-slot:other="otherSlotProps">
    slotProps NÃO está disponível aqui!
  </template>
</current-user>

Sempre que houver vários slots, use a sintaxe completa com base em <template> para todos os slots:

<current-user>
  <template v-slot:default="slotProps">
    {{ slotProps.user.firstName }}
  </template>

  <template v-slot:other="otherSlotProps">
    ...
  </template>
</current-user>

Desestruturando Props do Slot

Internamente, os slots com escopo trabalham envolvendo seu conteúdo em uma função passando um único argumento:

function (slotProps) {
  // ... conteúdo do slot ...
}

Isso significa que o valor de v-slot pode realmente aceitar qualquer expressão JavaScript válida que possa aparecer na posição de argumento em uma definição de função. Então, em ambientes suportados (componentes single-file ou browsers modernos), você também pode usar desestruturação de objetos do ES2015 para usar props específicos de slots, assim:

<current-user v-slot="{ user }">
  {{ user.firstName }}
</current-user>

Isso pode tornar o template bem mais limpo, especialmente quando o slot fornece muitas props. Também abre outras possibilidades, tais como renomear props, e.g. user para person:

<current-user v-slot="{ user: person }">
  {{ person.firstName }}
</current-user>

Você pode até mesmo definir fallbacks, para serem usados no caso de um prop do slot ser undefined:

<current-user v-slot="{ user = { firstName: 'Guest' } }">
  {{ user.firstName }}
</current-user>

Slots de nomes dinâmicos

Novo na versão 2.6.0+

Argumentos de diretiva dinâmicos também funciona no v-slot, permitindo a definição de nomes de slots dinâmicos:

<base-layout>
  <template v-slot:[dynamicSlotName]>
    ...
  </template>
</base-layout>

Forma Abreviada para Slots Nomeados

Novo na versão 2.6.0+

Semelhante ao v-on e v-bind, v-slot também possui uma forma abreviada, substituindo tudo antes do argumento (v-slot:) pelo símbolo especial #. Por exemplo, v-slot:header pode ser reescrito como #header:

<base-layout>
  <template #header>
    <h1>Aqui pode estar um título da página</h1>
  </template>

  <p>Um parágrafo para o conteúdo principal.</p>
  <p>E um outro parágrafo.</p>

  <template #footer>
    <p>Aqui estão algumas informações de contato</p>
  </template>
</base-layout>

No entanto, assim como com outras diretivas, a abreviação só está disponível quando um argumento é fornecido. Isso significa que a seguinte sintaxe é inválida:

<!-- Isso acionará um aviso -->
<current-user #="{ user }">
  {{ user.firstName }}
</current-user>

Em vez disso, você sempre deve especificar um nome para o slot se desejar usar o atalho:

<current-user #default="{ user }">
  {{ user.firstName }}
</current-user>

Outros exemplos

Props de slot nos permitem transformar slots em templates reutilizáveis que podem renderizar diferentes conteúdos com base na inserção de props. Isso é mais útil quando você está projetando um componente reutilizável que encapsula a lógica de dados, enquanto permite que o componente pai consumidor personalize parte de seu layout.

Por exemplo, estamos implementando um componente <todo-list> que contém o layout e a lógica de filtragem para uma lista:

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

Em vez de codificar o conteúdo para cada todo, podemos deixar que o componente pai assuma o controle fazendo de todos todo um slot, então vinculando todo como uma prop de slot:

<ul>
  <li
    v-for="todo in filteredTodos"
    v-bind:key="todo.id"
  >
    <!--
    Nós temos um slot para cada _todo_ passando o objeto
    `todo` como uma prop de slot.
    -->
    <slot name="todo" v-bind:todo="todo">
      <!-- conteúdo fallback -->
      {{ todo.text }}
    </slot>
  </li>
</ul>

Agora, quando usamos o componente <todo-list>, podemos opcionalmente definir um <template> alternativo para itens do todo, mas com acesso aos dados do filho:

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

No entanto, mesmo isso apenas arranha a superfície do que slots com escopo são capazes de fazer. Para exemplos completos e reais de uso, recomendamos buscar mais sobre bibliotecas como Vue Virtual Scroller, Vue Promised, e Portal Vue.

Sintaxe Obsoleta

A diretiva v-slot foi introduzida no Vue 2.6.0, oferecendo uma API melhorada e alternativa aos atributos slot e slot-scope ainda suportados. O raciocínio completo para introduzir o v-slot é descrito neste RFC. Os atributos slot e slot-scope continuarão a ser suportados em todas as futuras versões 2.X, mas serão oficialmente descontinuados e serão eventualmente removidos no Vue 3.

Slots Nomeados com o atributo slot

Obsoleta no 2.6.0+. Veja aqui a nova e recomendada sintaxe.

Para passar conteúdos para os slots nomeados a partir do pai, use o atributo especial slot no <template> (usando o componente <base-layout> descrito aqui como exemplo):

<base-layout>
  <template slot="header">
    <h1>Aqui pode ser o título da página</h1>
  </template>

  <p>Um parágrafo para o conteúdo principal.</p>
  <p>E um outro parágrafo.</p>

  <template slot="footer">
    <p>Aqui algumas informações de contato</p>
  </template>
</base-layout>

Ou o atributo slot que também pode ser usado diretamente em um elemento normal:

<base-layout>
  <h1 slot="header">Aqui pode ser o título da página</h1>

  <p>Um parágrafo para o conteúdo principal.</p>
  <p>E um outro parágrafo.</p>

  <p slot="footer">Aqui algumas informações de contato</p>
</base-layout>

Ainda pode haver um slot sem nome, que é o slot default que serve como um agrupador para qualquer conteúdo que esteja solto. Em ambos exemplos acima, o HTML renderizado seria:

<div class="container">
  <header>
    <h1>Aqui pode ser o título da página</h1>
  </header>
  <main>
    <p>Um parágrafo para o conteúdo principal.</p>
    <p>E um outro parágrafo.</p>
  </main>
  <footer>
    <p>Aqui algumas informações de contato</p>
  </footer>
</div>

Slots de Escopo Definido com o atributo slot-scope

Obsoleta no 2.6.0+. Veja aqui a nova e recomendada sintaxe.

Para receber as props passadas para um slot, o componente pai pode usar o <template> com o atributo slot-scope (usando o <slot-exemplo> descrito no exemplo aqui):

<slot-example>
  <template slot="default" slot-scope="slotProps">
    {{ slotProps.msg }}
  </template>
</slot-example>

Aqui, o slot-scope declara o objeto props recebido como a variável slotProps, e o torna disponível dentro do escopo do <template>. Você pode nomear o slotProps da maneira que preferir, semelhante a argumentos de funções em JavaScript.

Aqui slot=default pode ser omitido como está implicitado:

<slot-example>
  <template slot-scope="slotProps">
    {{ slotProps.msg }}
  </template>
</slot-example>

O atributo slot-scope também pode ser usado diretamente em elementos que não sejam <template> (incluindo componentes):

<slot-example>
  <span slot-scope="slotProps">
    {{ slotProps.msg }}
  </span>
</slot-example>

O valor de slot-scope pode aceitar qualquer expressão JavaScript válida que pode aparecer na posição de argumento de uma definição de função. Isso significa que em ambientes suportados (single-file components ou browsers modernos) você também pode usar desestruturação de objetos do ES2015 na expressão, da seguinte forma:

<slot-example>
  <span slot-scope="{ msg }">
    {{ msg }}
  </span>
</slot-example>

Usando o <todo-list> descrito aqui como um exemplo, aqui está o uso equivalente usando 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>