Teleport组件
什么是Teleport
Vue 鼓励我们通过将 UI 和相关行为封装到组件中来构建 UI。我们可以将它们嵌套在另一个内部,以构建一个组成应用程序 UI 的树。
然而,有时组件模板的一部分逻辑上属于该组件,而从技术角度来看,最好将模板的这一部分移动到 DOM 中 Vue app 之外的其他位置。
Teleport是将组件模板内容移动到组件模板之外的DOM中,然而组件模板中使用到的data逻辑仍将在该组件汇中定义
案例:控制Toast弹窗的显示和隐藏
该案例中的组件,该组件中有一个button元素来触发Toast弹框的打开,还有一个具有类为.toast_wrap的div元素,该div元素包含Toast的所有内容
app.vue
<template> <div> <toast></toast> </div> </template> <script> import toast from "./components/toast.vue" export default { components:{ toast } } </script> <style lang="less" scoped> </style>
toast.vue
<template> <div class="setup"> <button class="btn">打开toast</button> <div class="toast_wrap"> <div class="toast_msg"> toast </div> </div> </div> </template> <script> export default { } </script> <style scoped> .setup{ width: 500px; height: 500px; border: 1px solid #666; margin: 10px auto; position: relative; } .btn{ background-color: skyblue; font-size: 12px; color: #fff; padding: 10px 20px; border-radius: 5px; position: absolute; } .toast_wrap{ position: absolute; top: 20%; left: 30%; width: 60%; text-align: center; } .toast_msg{ background-color: #ddd; color: white; padding: 2px 5px; border-radius: 5px; } </style>
此时我们可以看到toast部分是在setup下,其绝对定位参照的父元素也是这个div元素,
如果此时我们想让这个toast参照body元素为父元素,我们可以使用teleport组件,通过to属性,该组件的内容渲染到body下,但是teleport的状态visilibe,又是完全在vue组件内部控制
<template> <div class="setup"> <button class="btn">打开toast</button> <teleport to='body'> <div class="toast_wrap"> <div class="toast_msg"> toast </div> </div> </teleport> </div> </template>
此时我们就会看到toast这部分在body中,其大小随着body的变化而变化
to的作用:告诉vue,Teleport这个组件下的html渲染到body标签下
此时我们控制toast的显示和隐藏
<template> <div class="setup"> <button class="btn" @click="show">打开toast</button> <teleport to='body'> <div class="toast_wrap" v-if="change"> <div class="toast_msg"> toast </div> </div> </teleport> </div> </template> <script> import {ref} from "vue" export default { setup(){ //弹框中使用到的数据逻辑 在当前组件中定义 只是将html结构渲染到to属性对应的标签下 let change=ref(false) let time; function show(){ change.value=true; clearTimeout(time) time=setTimeout(()=>{ change.value=false },2000) } return {change,show} } } </script> <style scoped> .setup{ width: 500px; height: 500px; border: 1px solid #666; margin: 10px auto; position: relative; } .btn{ background-color: skyblue; font-size: 12px; color: #fff; padding: 10px 20px; border-radius: 5px; position: absolute; } .toast_wrap{ position: absolute; top: 20%; left: 30%; width: 60%; text-align: center; } .toast_msg{ background-color: #ddd; color: white; padding: 2px 5px; border-radius: 5px; } </style>
此时我们点击之后显示,2秒钟过后又自动消失
Teleport作为一个自带的组件,接收to和disabled两个参数:
to: 类型string 需要props,必须是有效的查询选择器或htmlElement,指定teleport内容的目标元素
to的值可以id选择器、class选择器、data-*选择器
disabled: 类型boolean,可选属性,作用:禁用<teleport>的功能,意味着插槽内容不会移动到任何位置,而是在你的父组件中指定的<teleport>的位置渲染
index.html
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<div id="teleport_id"></div>
<div class="teleport_class"></div>
<div data-teleport></div>
<!-- built files will be auto injected -->
</body>
</html>
teleport.vue
<template> <div class="teleport_content"> <teleport to="#teleport_id"> <p>移动到id的盒子里</p> </teleport> <teleport to=".teleport_class"> <p>移动到class的盒子里{{name}}</p> </teleport> <teleport to="[data-teleport]"> <p>移动到自定义的盒子里</p> </teleport> <teleport to="[data-teleport]" :disabled="ableteport"> <p>移动到自定义的盒子里</p> </teleport> </div> </template> <script> import {ref} from 'vue' export default { setup(){ let ableteport=ref(true); return {ableteport} } } </script> <style lang="less" scoped> </style>
我们此时看一下teleport的冒泡事件
<template> <div class="teleport_content" @click="fun"> <teleport to="#teleport_id"> <p @click="fun1">移动到id的盒子里</p> </teleport> <teleport to=".teleport_class"> <p>移动到class的盒子里{{name}}</p> </teleport> <teleport to="[data-teleport]"> <p>移动到自定义的盒子里</p> </teleport> <teleport to="[data-teleport]" :disabled="ableteport"> <p>移动到自定义的盒子里</p> </teleport> </div> </template> <script> import {ref} from 'vue' export default { setup(){ let ableteport=ref(true); function fun(){ console.log("fun") }; function fun1(){ console.log("fun1") }; return {ableteport ,fun ,fun1} } } </script> <style lang="less" scoped> </style>
此时我们点击id的盒子,发现并没有冒泡到fun
当我们点击div的时候才会触发fun
元素上的事件会被阻断,不会冒泡到teleport中的组件内
Teleport和vue Components一起使用
如果<teleport>中包含vue组件,则它仍将是<teleport>父组件的逻辑子组件
案例:
控制model模态框的打开和关闭
虽然在body标签下渲染model组件,它仍将是App组件的子级,并将从中接收show 属性,意味着来自父组件的注入按预期工作,并且子组件将嵌套在vue中的父组件之下,而不是放在实际内容移动到的位置
app.vue
<template> <div> <model></model> </div> </template> <script> import model from "./components/model.vue" export default { components:{ model } } </script> <style lang="less" scoped> </style>
model.vue
<template>
<div class="setup">
<button class="btn" @click="show=true" >打开model</button>
<teleport to="body">
<mod :show="show" @close="show=false">
<template #content>
<h3>model</h3>
</template>
</mod>
</teleport>
</div>
</template>
<script>
import mod from "./mod.vue"
import Teleport from './teleport.vue'
import {ref} from "vue"
export default {
components:{
mod
},
setup(){
let show=ref(false)
return {show}
}
}
</script>
<style scoped>
.setup{
width: 500px;
height: 500px;
border: 1px solid #666;
margin: 10px auto;
position: relative;
}
.btn{
background-color: skyblue;
font-size: 12px;
color: #fff;
padding: 10px 20px;
border-radius: 5px;
position: absolute;
}
</style>
mod.vue
<template> <div class="mod_warp" v-if="show"> <div class="mod_container"> <slot name="content"></slot> <button class="mod_button" @click="$emit('close')">隐藏</button> </div> </div> </template> <script> export default { props:{ show:Boolean } } </script> <style scoped> .mod_warp{ position: absolute; z-index: 12; top: 0; left:0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.3); } .mod_container{ width: 300px; height: 100px; background-color: #FFF; border-radius: 10px; position: absolute; left: 0; top: 0; bottom: 0; right: 0; margin: auto; display: flex; justify-content: space-between; } .mod_button{ width: 50px; height: 30px; } </style>
此时我们点击一下就会出现弹窗,然后点击隐藏就会关闭
即使在不同的地方渲染子组件mod,它仍是model的子组件,并从中接收show属性,意味着父组件的注入按预期工作,并且子组件将嵌套在vue的父组件下,而不是放在实际内容移动的位置