题目:
看到这个题大致的意思是 vue 有提供一个内置组件,用于将插槽内容渲染到另外的一个 Dom
元素中,变成改 Dom
元素的一部分。单看描述谁知道是什么鬼意思是吧?让我们一起来看看吧。
分析:
让我们来看看 vue 官方文档对 teleport 的介绍吧。一个内置组件,可以将组件内部的一部分模板,传送到该组件 Dom 结构的外层去。并且还举了一个模态框的例子。巴拉巴拉巴拉,其实我们可以简单去理解,它就像一个哆啦 A 梦的任意门,能够将东西瞬移到其他地方。
举例:
俗话说得好,好记性不如烂笔头是吧,那么前端有啥烂笔头,不就你那副玩烂的键盘么。
首先我们定义了一个父组件,它包含一个子组件Child
。
javascript
// 父组件
<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
模态框组件。
javascript
// 子组件
<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
模态框组件的逻辑。
javascript
// 模态框
<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>
分析:
以上代码就是一个很简单的点击弹窗按钮弹出模态框,点击模态框关闭按钮关闭模态框的功能。让我们来看一下它此时的渲染情况。
虽然模态框正常渲染了,但是此时我们可以看到,这个模态框的层级是经历了很多层的,假设它外层的组件如果设置的 z-index
层级每个都不同,那我们是不是要不停去设置我们模态框的 z-index
属性来适配这种情况。如果写了一些特殊的样式,是不是也会不小心破坏模态框的布局。也就是说从用户感知的角度来说,my-modal
应该是一个独立的组件。从 Dom
结构应该完全剥离 Vue 顶层挂载的组件 Dom。简单来讲就是我既希望在组件内部用 my-modal
,但是又不希望渲染在嵌套的组件 Dom
中。所以这就是teleport设置的初衷。
那么使用teleport,根据官方所描述应该会被传送到body标签下让我们来试试看。
javascript
<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
标签下了,不禁感慨,还有这种操作。
解决:
回到我们的挑战,所以这里我们要解决这个问题的话,直接用 teleport
将 span
渲染到 body
即可。
javascript
<script setup>
const msg = "Hello World";
</script>
<template>
<!-- 使用 teleport 将内容渲染到 body 的子元素中 -->
<teleport to="body">
<span>{{ msg }}</span>
</teleport>
</template>