21.内置组件 Teleport
什么是Teleport?——<Teleport>
是一个内置组件,它是一种能够将我们的模板渲染至指定DOM节点,不受父级style、v-show等属性影响,但data、prop数据依旧能够共用的技术;类似于 React 的 Portal。
主要解决的问题 因为Teleport节点挂载在其他指定的DOM节点下,完全不受父级style样式影响
- teleport 是内置组件,可以直接在模板中使用,无需注册。
- 可以被打包工具 tree-shake。所以它们只会在被使用的时候被引入。
- 需要直接主动访问(获取)它们的场景,也可以将它们显性导入。
21.1 基本用法
有时我们可能会遇到这样的场景:一个组件模板的一部分在逻辑上从属于该组件,但从整个应用视图的角度来看,它在 DOM 中应该被渲染在整个 Vue 应用外部的其他地方。
这类场景最常见的例子就是全屏的模态框。理想情况下,我们希望触发模态框的按钮和模态框本身是在同一个组件中,因为它们都与组件的开关状态有关。但这意味着该模态框将与按钮一起渲染在应用 DOM 结构里很深的地方。这会导致该模态框的 CSS 布局代码很难写。
父组件App.vue
<template>
<div class="app">
<h3>我是App组件</h3>
<Child></Child>
</div>
</template>
<script>
export default {
name: 'App',
};
</script>
<script setup>
import Child from './components/Child.vue';
</script>
<style>
.app {
background-color: gray;
padding: 10px;
/* position: relative; */
}
</style>
子组件Child.vue
<template>
<div class="child">
<h3>我是Child组件</h3>
<button @click="isShow = true">打开遮罩层</button>
<div class="mask" v-if="isShow">
<div class="tip">
这是一个提示
<button @click="isShow = false">关闭</button>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'Child',
};
</script>
<script setup>
let isShow = $ref(false);
</script>
<style scoped>
.mask {
position: absolute;
z-index: 999;
width: 100%;
height: 100%;
top: 0;
left: 0;
background-color: rgba(0, 0, 0, 0.3);
}
.tip {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
margin: auto;
}
</style>
现在来看没有问题
可是如果父元素加一个定位:
position: relative;
遮罩层就会变成这样
以后嵌套组件变多了,遮罩组件的父级组件存在定位语句是很正常的。如果能把这个组件渲染到body
标签的子标签就好了。这时就可以使用Teleport
组件。
-
to - string :必传
挂载的目标,只能是父级标签。兄弟、子级都会报错。
挂载目标必须是有效的查询选择器或 HTMLElement -
disabled - boolean :非必传
-
是否禁用。true则挂载到目标节点下,false为当前位置。
-
动态变化 disabled 值时,只是位置会变动,内容并不会销毁重新渲染!
-
<Teleport>
接收一个 to
prop 来指定传送的目标。to
的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue “把以下模板片段传送到 body
标签下”。
父组件App.vue
中使用组件的位置改成这样。
<Teleport to="body">
<Child></Child>
</Teleport>
可以发现这个组件确实被渲染到body
标签下面了。
提示
<Teleport>
挂载时,传送的to
目标必须已经存在于 DOM 中。理想情况下,这应该是整个 Vue 应用 DOM 树外部的一个元素。如果目标元素也是由 Vue 渲染的,你需要确保在挂载<Teleport>
之前先挂载该元素。以下teleport例子将会报错!
<div class="guazai" v-if="showData">teleport目标元素</div> <div> <teleport to=".guazai">demo</teleport> </div> <script lang='ts'> import { defineComponent } from 'vue' export default defineComponent({ data(){ return { showData:false } }, mounted() { setTimeout(() => { this.showData = true }, 1000*1); }, }) </script>
以下官方代码列举写法。但是前面说过 “ 挂载目标必须是有效的查询选择器或 HTMLElement ”
实际填h1 h2等原生标签、以及自定义的标签都是没问题的。以下应该是官方文档的一个纰漏。
<!-- 正确 --> <teleport to="#some-id" /> <teleport to=".some-class" /> <teleport to="[data-teleport]" /> <!-- 错误 --> <teleport to="h1" /> <teleport to="some-string" />
关于样式: teleport
teleport挂载的目标即使不在当前组件,scoped 内的样式在 teleport 中是生效的。
原因:scoped 生成的唯一属性会作用于 teleport 内的各元素
21.2 官方案例
试想下面这样的 HTML 结构:
<div class