Vue 3 中的“传送门”之组件Teleport

444 阅读4分钟

Teleport简介

Vue 3 的 Teleport 组件提供了强大的组件渲染能力,可以将组件的内容精确地渲染到任意位置,无论其在组件树中的层级如何。本篇博客将深入探讨 Teleport 的高级技巧和在复杂场景下的应用,带您进一步提升 Vue 开发技能。

基本用法

有时我们可能会遇到这样的场景:一个组件模板的一部分在逻辑上从属于该组件,但从整个应用视图的角度来看,它在 DOM 中应该被渲染在整个 Vue 应用外部的其他地方。

这类场景最常见的例子就是全屏的模态框。理想情况下,我们希望触发模态框的按钮和模态框本身是在同一个组件中,因为它们都与组件的开关状态有关。但这意味着该模态框将与按钮一起渲染在应用 DOM 结构里很深的地方。这会导致该模态框的 CSS 布局代码很难写。

试想下面这样的 HTML 结构:

template

<div class="outer">
  <h3>Tooltips with Vue 3 Teleport</h3>
  <div>
    <MyModal />
  </div>
</div>

接下来我们来看看 <MyModal> 的实现:

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

这个组件中有一个 <button> 按钮来触发打开模态框,和一个 class 名为 .modal 的 <div>,它包含了模态框的内容和一个用来关闭的按钮。

当在初始 HTML 结构中使用这个组件时,会有一些潜在的问题:

  • position: fixed 能够相对于浏览器窗口放置有一个条件,那就是不能有任何祖先元素设置了 transformperspective 或者 filter 样式属性。也就是说如果我们想要用 CSS transform 为祖先节点 <div class="outer"> 设置动画,就会不小心破坏模态框的布局!
  • 这个模态框的 z-index 受限于它的容器元素。如果有其他元素与 <div class="outer"> 重叠并有更高的 z-index,则它会覆盖住我们的模态框。

<Teleport> 提供了一个更简单的方式来解决此类问题,让我们不需要再顾虑 DOM 结构的问题。让我们用 <Teleport> 改写一下 <MyModal>

<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 prop 来指定传送的目标。to 的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段传送到 body 标签下”。

TIP

<Teleport> 挂载时,传送的 to 目标必须已经存在于 DOM 中。理想情况下,这应该是整个 Vue 应用 DOM 树外部的一个元素。如果目标元素也是由 Vue 渲染的,你需要确保在挂载 <Teleport> 之前先挂载该元素。

示例

动态组件结合使用

Teleport 不仅可以渲染静态内容,还可以与动态组件相结合,实现在不同位置动态渲染不同的组件。我们将通过一个示例来演示这一技巧。

<template>
  <div>
    <h1>Teleport 和动态组件高级示例</h1>
    <Teleport :to="teleportTarget">
      <component :is="getCurrentComponent"></component>
    </Teleport>
    <button @click="toggleComponent">切换组件</button>
  </div>
</template>

<script>
import { ref, defineComponent, Teleport } from 'vue';

export default defineComponent({
  setup() {
    const currentComponent = ref('ComponentA');
    const teleportTarget = ref(null);

    const toggleComponent = () => {
      currentComponent.value = currentComponent.value === 'ComponentA' ? 'ComponentB' : 'ComponentA';
    };

    const getCurrentComponent = () => () => import(`./components/${currentComponent.value}.vue`);

    return {
      currentComponent,
      teleportTarget,
      toggleComponent,
      getCurrentComponent,
    };
  },
});
</script>

多层级嵌套组件

Teleport 在处理多层级嵌套组件时非常有用。例如,当一个组件嵌套在多个层级的父组件中,但需要在另一个层级的 DOM 元素中渲染,Teleport 可以轻松解决这个问题。

<template>
  <div>
    <h1>Teleport 和多层级嵌套组件示例</h1>
    <ParentComponent>
      <Teleport :to="teleportTarget">
        <NestedComponent />
      </Teleport>
    </ParentComponent>
    <div ref="teleportTarget"></div>
  </div>
</template>

<script>
import { ref, defineComponent, Teleport } from 'vue';
import ParentComponent from './components/ParentComponent.vue';
import NestedComponent from './components/NestedComponent.vue';

export default defineComponent({
  components: {
    ParentComponent,
    NestedComponent,
    Teleport,
  },
  setup() {
    const teleportTarget = ref(null);

    return {
      teleportTarget,
    };
  },
});
</script>

条件渲染的用法

在某些情况下,我们可能需要根据条件动态渲染 Teleport 组件,或者在 Teleport 组件中使用条件渲染。下面是一些常见的高级用法示例。

<template>
  <div>
    <h1>Teleport 和条件渲染高级示例</h1>
    <Teleport v-if="shouldRender" :to="teleportTarget">
      <template v-if="condition">
        <p>条件渲染示例</p>
      </template>
      <template v-else>
        <p>备用渲染示例</p>
      </template>
    </Teleport>
    <button @click="toggleRender">切换渲染</button>
  </div>
</template>

<script>
import { ref, defineComponent, Teleport } from 'vue';

export default defineComponent({
  setup() {
    const shouldRender = ref(true);
    const condition = ref(true);
    const teleportTarget = ref(null);

    const toggleRender = () => {
      shouldRender.value = !shouldRender.value;
    };

    return {
      shouldRender,
      condition,
      teleportTarget,
      toggleRender,
    };
  },
});
</script>

这些高级技巧和复杂场景的示例为您提供了更深入的 Teleport 使用指南。通过理解这些示例并将其应用到您的项目中,您将能够更好地利用 Teleport 组件的灵活性和强大功能。

请注意,Teleport 组件还有许多其他功能和用法,如使用 渲染到 body 标签、使用动态的渲染目标等。在实际开发中,根据具体需求,您可以进一步探索 Teleport 的更多功能和应用。