Skip to content

Teletransporte

<Teleport> é um componente embutido que permite-nos "teletransportar" uma parte de um modelo de marcação do componente para um nó de DOM que existe fora da hierarquia do DOM deste componente.

Uso Básico

Algumas vezes podemos correr para os seguintes cenários: uma parte de um modelo de marcação do componente pertence a ele logicamente, mas a partir de um ponto de vista visual, ele deve ser exibido noutro lugar no DOM, fora da aplicação de Vue.

O exemplo mais comum disto é quando construimos um modal de tela cheia. Idealmente, queremos que o botão do modal e o próprio modal vivam dentro do mesmo componente, já que estão ambos relacionados ao estado abertura e fechamento do modal. Mas isto significa que o modal será interpretado junto do botão, profundamente encaixado na hierarquia do DOM da aplicação. Isto pode criar algumas questões complicadas quando posicionamos o modal através de CSS.

Considere a seguinte estrutura de HTML:

template
<div class="outer">
  <h3>Vue Teleport Example</h3>
  <div>
    <MyModal />
  </div>
</div>

E cá está a implementação do <MyModal>:

vue
<script setup>
import { ref } from 'vue'

const open = ref(false)
</script>

<template>
  <button @click="open = true">Open Modal</button>

  <div v-if="open" class="modal">
    <p>Hello from the modal!</p>
    <button @click="open = false">Close</button>
  </div>
</template>

<style scoped>
.modal {
  position: fixed;
  z-index: 999;
  top: 20%;
  left: 50%;
  width: 300px;
  margin-left: -150px;
}
</style>
vue
<script>
export default {
  data() {
    return {
      open: false
    }
  }
}
</script>

<template>
  <button @click="open = true">Open Modal</button>

  <div v-if="open" class="modal">
    <p>Hello from the modal!</p>
    <button @click="open = false">Close</button>
  </div>
</template>

<style scoped>
.modal {
  position: fixed;
  z-index: 999;
  top: 20%;
  left: 50%;
  width: 300px;
  margin-left: -150px;
}
</style>

O componente contém um <button> para acionar a abertura do modal, e a <div> uma classe .modal, a qual conterá o conteúdo do modal e um botão que se fecha a si mesmo.

Quando usamos este componente dentro da estrutura inicial de HTML, existe um número de potenciais questões:

  • position: fixed apenas coloca o elemento relativo a janela de exibição quando nenhum elemento ancestral tiver a propriedade transform, perspective ou filter definida. Se, por exemplo, tencionamos animar o ancestral <div class="outer"> com a transformação de CSS, quebraria a disposição do modal!

  • O z-index do modal é restrito pelos seus elementos que o contém. Se houver um outro elemento que sobrepõem-se ao <div class="outer"> e tiver um z-index superior, ele cobriria o nosso modal.

<Teleport> fornece um maneira clara de os contornar, permitindo-nos fugir da estrutura de DOM encaixada. Vamos modificar <MyModal> para usar <Teleport>:

template
<button @click="open = true">Open Modal</button>

<Teleport to="body">
  <div v-if="open" class="modal">
    <p>Hello from the modal!</p>
    <button @click="open = false">Close</button>
  </div>
</Teleport>

O alvo do to de <Teleport> espera uma sequência de caracteres de seletor de CSS ou um nó de DOM real. Aqui, estamos essencialmente dizendo a Vue para "teletransportar este fragmento de modelo de marcação para o marcador body".

Tu podes clicar no botão abaixo e inspecionar o marcador <body> através da ferramenta de programação do navegador:

Tu podes combinar <Teleport> com <Transition> para criar modais animados - consulte o exemplo.

Dica

O alvo to da teletransportação já deve estar no DOM quando o componente <Teleport> for montado. Idealmente, isto deve ser um elemento fora da aplicação de Vue inteira. Se virando um outro elemento interpretado pela Vue, precisas certificar-te de que o elemento seja montado antes do <Teleport>.

Usando com Componentes

<Teleport> apenas altera a estrutura do DOM interpretada - ele não afeta a hierarquia lógica dos componentes. Isto é, se <Teleport> contiver um componente, este componente continuará a ser um filho lógico do componente pai contendo o <Teleport>. A passagem de propriedades e emissão de evento continuarão a funcionar da mesma maneira.

Isto também significa que as injeções de um componente pai funcionam como esperado, e que o componente filho será encaixado abaixo do componente pai na Ferramenta de Programação de Vue, ao invés de ser colocado para onde o conteúdo real foi movido.

Desativando o Teletransporte

Em alguns casos, podemos querer desativar condicionalmente <Teleport>. Por exemplo, podemos interpretar um componente como uma sobreposição para a área de trabalho, mas em linha no telemóvel. <Teleport> suporta a propriedade disabled que pode ser alternada dinamicamente:

template
<Teleport :disabled="isMobile">
  ...
</Teleport>

Onde o estado isMobile pode ser atualizado dinamicamente detetando mudanças de consulta de media.

Vários Teletransporte sobre o Mesmo Alvo

Um caso de uso comum seria um componente <Modal> reutilizável, com o potencial para várias instâncias a serem ativadas ao mesmo tempo. Para este tipo de cenário, vários componentes <Teleport> podem montar seus conteúdos para o mesmo elemento de alvo. A ordem será um simples adição - depois as montagens serão localizadas depois dos anteriores dentro do elemento de alvo.

Dada o seguinte uso:

template
<Teleport to="#modals">
  <div>A</div>
</Teleport>
<Teleport to="#modals">
  <div>B</div>
</Teleport>

O resultado interpretado seria:

html
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>

Relacionado ao

Teletransporte has loaded