一、前言
Vue.js 的核心思想之一是组件化,组件就是 DOM 的映射,我们通过嵌套的组件构成了一个组件应用程序的树。但是,有些时候组件模板的一部分在逻辑上属于该组件,而从技术角度来看,最好将模板的这一部分移动到应用程序之外的其他位置。一个常见的场景是创建一个包含全屏模式的对话框组件。在大多数情况下,我们希望对话框的逻辑存在于组件中,但是对话框的定位 CSS 是一个很大的问题,它非常容易受到外层父组件的 CSS 影响。假设我们有这样一个 dialog 组件,用按钮来管理一个 dialog:
<template>
<div v-show="visible" class="dialog">
<div class="dialog-body">
<p>I'm a dialog!</p>
<button @click="visible=false">Close</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
visible: false
}
},
methods: {
show() {
this.visible = true
}
}
}
</script>
<style>
.dialog {
position: absolute;
top: 0; right: 0; bottom: 0; left: 0;
background-color: rgba(0,0,0,.5);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
}
.dialog .dialog-body {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: white;
width: 300px;
height: 300px;
padding: 5px;
}
</style>
<template>
<button @click="showDialog">Show dialog</button>
<Dialog ref="dialog"></Dialog>
</template>
<script>
import Dialog from './components/dialog'
export default {
components: {
Dialog
},
methods: {
showDialog() {
this.$refs.dialog.show()
}
}
}
</script>
dialog 组件使用的是 position:absolute 绝对定位的方式,如果它的父级 DOM 有 position 不为 static 的布局方式,那么 dialog 的定位就受到了影响,不能按预期渲染了。所以一种好的解决方案是把 dialog 组件渲染的这部分 DOM 挂载到 body 下面,这样就不会受到父级样式的影响了。Vue.js 3.0 把这一能力内置到内核中,提供了一个内置组件 Teleport,它可以轻松帮助我们实现上述需求:
<template>
<button @click="showDialog">Show dialog</button>
<teleport to="body"&