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的父组件下,而不是放在实际内容移动的位置

 

posted @ 2021-11-08 21:07  keyeking  阅读(427)  评论(0编辑  收藏  举报