likes
comments
collection
share

Vue3你真的熟悉吗?04-哆啦A梦的任意门-teleport

作者站长头像
站长
· 阅读数 4

原题链接

题目:

Vue3你真的熟悉吗?04-哆啦A梦的任意门-teleport

看到这个题大致的意思是 vue 有提供一个内置组件,用于将插槽内容渲染到另外的一个 Dom 元素中,变成改 Dom 元素的一部分。单看描述谁知道是什么鬼意思是吧?让我们一起来看看吧。

Vue3你真的熟悉吗?04-哆啦A梦的任意门-teleport

分析:

Vue3你真的熟悉吗?04-哆啦A梦的任意门-teleport

让我们来看看 vue 官方文档对 teleport 的介绍吧。一个内置组件,可以将组件内部的一部分模板,传送到该组件 Dom 结构的外层去。并且还举了一个模态框的例子。巴拉巴拉巴拉,其实我们可以简单去理解,它就像一个哆啦 A 梦的任意门,能够将东西瞬移到其他地方。

Vue3你真的熟悉吗?04-哆啦A梦的任意门-teleport

举例:

俗话说得好,好记性不如烂笔头是吧,那么前端有啥烂笔头,不就你那副玩烂的键盘么。

Vue3你真的熟悉吗?04-哆啦A梦的任意门-teleport

首先我们定义了一个父组件,它包含一个子组件Child

// 父组件
<template>
  <div class="layout">
    <Child />
    <div class="box">首页的一个盒子</div>
  </div>
</template>

<script setup>
import Child from "./components/Child/index.vue";
</script>

<style lang="scss" scoped>
.layout {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  width: 100%;
  height: 100%;

  .box {
    z-index: 4;
    display: flex;
    align-items: center;
    width: 200px;
    height: 200px;
    background-color: pink;
  }
}
</style>

然后我们定义一个 Child 组件,里面又引用了 MyModal 模态框组件。

// 子组件
<template>
  <div class="child">
    <div class="btn" @click="handleOpen" v-if="!isVisible">点击弹窗</div>
    <div class="content"><MyModal v-model:visible="isVisible" /></div>
  </div>
</template>

<script setup>
import { ref } from "vue";
import MyModal from "../my-modal/index";

const isVisible = ref(false);
const handleOpen = () => {
  isVisible.value = true;
};
</script>

<style lang="scss" scoped>
.child {
  .btn {
    cursor: pointer;
    padding: 5px 10px;
    margin-bottom: 10px;
    color: #1e80ff;
    border: 1px solid rgba(30, 128, 255, 0.3);
    border-radius: 5px;
    background-color: rgba(30, 128, 255, 0.05);
  }
}
</style>

最后是 MyModal 模态框组件的逻辑。

// 模态框
<template>
  <div>
    <div class="modal" v-if="visible">
      <div class="header">
        <div class="btn" @click="handleClose">X</div>
      </div>
      <div class="content">我是弹窗内容</div>
    </div>
  </div>
</template>

<script setup name="MyModal">
import { defineProps, defineEmits } from "vue";

const emit = defineEmits(["update:visible"]);
defineProps({
  visible: {
    type: Boolean,
    default: false,
  },
});

const handleClose = () => {
  emit("update:visible", false);
};
</script>

<style lang="scss" scoped>
.modal {
  position: fixed;
  z-index: 999;
  top: 20%;
  left: 50%;
  width: 300px;
  margin-left: -150px;
  width: 200px;
  height: 200px;
  background-color: #ccc;
  .header {
    display: flex;
    justify-content: flex-end;
    .btn {
      cursor: pointer;
    }
  }
}
</style>

分析:

以上代码就是一个很简单的点击弹窗按钮弹出模态框,点击模态框关闭按钮关闭模态框的功能。让我们来看一下它此时的渲染情况。

Vue3你真的熟悉吗?04-哆啦A梦的任意门-teleport

虽然模态框正常渲染了,但是此时我们可以看到,这个模态框的层级是经历了很多层的,假设它外层的组件如果设置的 z-index 层级每个都不同,那我们是不是要不停去设置我们模态框的 z-index 属性来适配这种情况。如果写了一些特殊的样式,是不是也会不小心破坏模态框的布局。也就是说从用户感知的角度来说,my-modal 应该是一个独立的组件。从 Dom 结构应该完全剥离 Vue 顶层挂载的组件 Dom。简单来讲就是我既希望在组件内部用 my-modal,但是又不希望渲染在嵌套的组件 Dom 中。所以这就是teleport设置的初衷。

Vue3你真的熟悉吗?04-哆啦A梦的任意门-teleport

那么使用teleport,根据官方所描述应该会被传送到body标签下让我们来试试看。

<template>
  <div>
    <teleport to="body">
      <div class="modal" v-if="visible">
        <div class="header">
          <div class="btn" @click="handleClose">X</div>
        </div>
        <div class="content">我是弹窗内容</div>
      </div>
    </teleport>
  </div>
</template>

<script setup name="MyModal">
import { defineProps, defineEmits } from "vue";

const emit = defineEmits(["update:visible"]);
defineProps({
  visible: {
    type: Boolean,
    default: false,
  },
});

const handleClose = () => {
  emit("update:visible", false);
};
</script>

<style lang="scss" scoped>
.modal {
  position: fixed;
  z-index: 999;
  top: 20%;
  left: 50%;
  width: 300px;
  margin-left: -150px;
  width: 200px;
  height: 200px;
  background-color: #ccc;
  .header {
    display: flex;
    justify-content: flex-end;
    .btn {
      cursor: pointer;
    }
  }
}
</style>

可以看到使用 teleport 后,真如任意门一样直接传送到 body 标签下了,不禁感慨,还有这种操作。 Vue3你真的熟悉吗?04-哆啦A梦的任意门-teleport

Vue3你真的熟悉吗?04-哆啦A梦的任意门-teleport

解决:

回到我们的挑战,所以这里我们要解决这个问题的话,直接用 teleportspan 渲染到 body 即可。

<script setup>
const msg = "Hello World";
</script>

<template>
  <!-- 使用 teleport 将内容渲染到 body 的子元素中 -->
  <teleport to="body">
    <span>{{ msg }}</span>
  </teleport>
</template>

最后:

好了那么,就到此为止吧。

我是Xwenhaha,一个不想只当切图仔的切图仔罢了。