跳转到内容

Teleport

<Teleport> 是一个内置组件,允许我们将组件模板的一部分 "传送" 到一个位于该组件DOM层次结构之外的DOM节点。

基本用法

有时我们可能会遇到以下场景:组件模板的一部分在逻辑上属于它,但从视觉角度来看,它应该显示在DOM中的其他位置,在Vue应用程序之外。

这种情况最常见的例子是在构建全屏模态时。理想情况下,我们希望模态的按钮和模态本身位于同一个组件中,因为它们都与模态的打开/关闭状态相关。但这意味着模态将与按钮一起渲染,深度嵌套在应用程序的DOM层次结构中。这可能会在通过CSS定位模态时产生一些棘手的问题。

考虑以下HTML结构。

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

以下是 <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>

该组件包含一个 <button> 用于触发模态的打开,一个带有 .modal 类的 <div>,其中将包含模态的内容和一个按钮来自动关闭。

当在初始HTML结构中使用此组件时,存在一些潜在问题

  • position: fixed 仅在没有任何祖先元素设置 transformperspectivefilter 属性时将元素定位到视口相对位置。例如,如果我们打算使用CSS转换来动画化祖先 <div class="outer">,这将破坏模态布局!

  • 模态的 z-index 受其容器元素的约束。如果有其他元素与 <div class="outer"> 相重叠并具有更高的 z-index,它将覆盖我们的模态。

<Teleport> 提供了一种简洁的方法来解决这个问题,通过允许我们跳出嵌套的DOM结构。让我们修改 <MyModal> 以使用 <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>

<Teleport>to 目标期望一个CSS选择器字符串或一个实际的DOM节点。在这里,我们实际上是告诉Vue "传送 这个模板片段到 body 标签"。

您可以通过点击下面的按钮并使用浏览器开发工具检查 <body> 标签

您可以将 <Teleport><Transition> 结合使用来创建动画模态框 - 请参阅 此处示例

技巧

teleport 的 to 目标必须在 <Teleport> 组件挂载时已经在 DOM 中。理想情况下,这应该是一个整个 Vue 应用程序之外的外部元素。如果您要针对由 Vue 渲染的另一个元素,请确保在该元素挂载之前挂载 <Teleport>

与组件一起使用

<Teleport> 只会更改渲染的 DOM 结构 - 它不会影响组件的逻辑层次结构。也就是说,如果 <Teleport> 包含一个组件,那么该组件将仍然是包含 <Teleport> 的父组件的逻辑子组件。属性传递和事件发射将继续以相同的方式工作。

这也意味着父组件的注入将按预期工作,并且子组件将在 Vue Devtools 中位于父组件下方,而不是实际内容移动到的位置。

禁用 Teleport

在某些情况下,我们可能希望有条件地禁用 <Teleport>。例如,我们可能希望将组件作为桌面上的覆盖层渲染,但在移动端上内联渲染。 <Teleport> 支持 disabled 属性,可以动态切换

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

其中 isMobile 状态可以通过检测媒体查询更改来动态更新。

同一目标上的多个 Teleport

一个常见的用例是可重用的 <Modal> 组件,可能存在多个实例同时活跃。在这种情况下,多个 <Teleport> 组件可以将它们的内容挂载到同一目标元素上。顺序将是简单的追加 -较晚挂载的将位于目标元素中较早挂载的后面。

给定以下用法

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

渲染结果将是

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

延迟 Teleport

在 Vue 3.5 及以上版本中,我们可以使用 defer 属性将 Teleport 的目标解析延迟到应用程序的其他部分已挂载。这允许 Teleport 针对一个由 Vue 渲染但位于组件树较晚部分的容器元素。

template
<Teleport defer to="#late-div">...</Teleport>

<!-- somewhere later in the template -->
<div id="late-div"></div>

请注意,目标元素必须在与 Teleport 同一挂载/更新周期中渲染 - 即如果 <div> 只在两秒后挂载,Teleport 仍然会报告错误。defer 与 mounted 生命周期钩子的工作方式相似。


相关

Teleport 已加载