一文了解——内置组件Teleport附带讲解源码(Vue.js)
前言
很多时候,要写一个弹窗类似的组件,总是会将部分心神消耗在position
的定位上。得考虑到父组件是怎么样的情况,会发生什么样子的事情,会不会对外面的组件样式造成影响。那有没有一点可以节约思绪的方法呢?那肯定是有的在Vue
的官网上有那么一个内置组件Teleport
就可以解决到这个问题。
Teleport的概念
直观一点摘要Vue
官网的一段话 : <Teleport>
是一个内置组件,它可以将一个组件内部的一部分模板“传送”到该组件的 DOM 。这就是他的最为简单的讲解。
创建文件以及代码内容
首先我们创建一下文件,格式如下:
代码可以直接拷贝
index.vue文件内容
<template>
<div class="body">
<h1>父级</h1>
<A></A>
</div>
</template>
<script setup lang="ts">
import A from './a.vue'
</script>
<style lang="scss">
.body {
background-color: rgb(81, 255, 0);
height: 50vh;
}
</style>
a.vue文件内容
<template>
<div class="dialog">
<header class="header">
<div>弹框</div>
<el-icon>
<CloseBold />
</el-icon>
</header>
<main class="main">
输出内容
</main>
<footer class="footer">
<el-button >取消</el-button>
<el-button >确定</el-button>
</footer>
</div>
</template>
<script setup lang='ts'>
import { ref, reactive } from 'vue'
</script>
<style lang="less" scoped>
.dialog {
width: 400px;
height: 400px;
background: #141414;
display: flex;
flex-direction: column;
position: absolute;
left: 50%;
top: 50%;
margin-left: -200px;
margin-top: -200px;
.header {
display: flex;
color: #CFD3DC;
border-bottom: 1px solid #636466;
padding: 10px;
justify-content: space-between;
}
.main {
flex: 1;
color: #CFD3DC;
padding: 10px;
}
.footer {
border-top: 1px solid #636466;
padding: 10px;
display: flex;
justify-content: flex-end;
}
}
</style>
展示出来的效果如下
可以看到在a.vue
的文件中,我们是通过绝对定位计算位置的方式来实现居中对齐的,但是如果在index.vue
文件中我们加入了相对定位 position: relative;
就会有这样子的效果:
没错,它来到了上面,这是因为绝对定位的父级没有加入相对定位的话,会相对于页面窗口去定位,而父组件使用了相对定位等类似的办法,就会改变他的锚点,会相对于父级去定位。
而Teleport就是对于这个问题做了一层处理。
那就让我们在index.vue
尝试加入 Teleport
吧!
Teleport的使用以及QA
我们只是需要直观的使用 Teleport
将原本的 A
组件包裹起来。
就可以做到我们想要的效果
代码可以直接拷贝
index.vue文件内容
<template>
<div class="body">
<h1>父级</h1>
<Teleport to="body">
<A></A>
</Teleport>
</div>
</template>
<script setup lang="ts">
import A from './a.vue'
</script>
<style lang="scss">
.body {
background-color: rgb(81, 255, 0);
height: 50vh;
position: relative;
}
</style>
Q:你可能会问 这个to
是啥?为什么Teleport
不需要引入?
A:首先来解答一下为什么Teleport
不需要引入?因为标题的时候已经写到了他本质上是一个内置组件,在后面中会讲到从他的源码中可以获悉,其实Teleport
并没有做出来什么渲染的操作,只是单纯的挂载到目标位置去。
Q:那么目标位置又是什么呢?
A:to
就是目标元素。<Teleport>
接收一个 to
prop 来指定传送的目标。to
的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。这段代码的作用就是告诉 Vue“把以下模板片段传送到 body
标签下”。
Q:那么又有一个问题了?假如我不需要这个了呢?我想要一个动态的可以做到我需要的时候在外面,不需要的时候挂载在父组件下面?
A:<Teleport>
也提供了选择,在某些场景下可能需要视情况禁用 <Teleport>
。举例来说,我们想要在桌面端将一个组件当做浮层来渲染,但在移动端则当作行内组件。我们可以通过对 <Teleport>
动态地传入一个 disabled
prop 来处理这两种不同情况。
如果看完这一段你觉得好像是理解了的话,其实这里还埋下了一个大坑。还记得第二个QUESTION中的这一句话嘛?to
的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。
<Teleport>
挂载时,传送的 to
目标必须已经存在于 DOM 中。理想情况下,这应该是整个 Vue 应用 DOM 树外部的一个元素。如果目标元素也是由 Vue 渲染的,你需要确保在挂载 <Teleport>
之前先挂载该元素。
Teleport的源码
首先你需要去github
上面拉取一份源码。
找到如下目录:
下拉到358行上下
teleport 会经过patch创建
在438行上下可以看到
在这里有一个判断:如果是TELEPORT
的元素的话,就会走Teleport
的创建路线,走执行process
方法
我们点进去看process方法
目录如下:
在如下的地方可以看到 TeleportImpl
里面有几个参数
主要讲的是process
和remove
Teleport的创建
首先我们去看process
中的创建可以看到在target
中我们获取到目标移动的dom
节点,然后往目标元素,挂载节点。
通过
mountChildren
的方法挂载子节点
在最后通过disabled
的判断来决定是否需要挂载到目标节点上
Teleport的更新
在判断的另一头就是更新的逻辑了
会判断两个点:一个是新节点disalbed
,还有一个是旧节点wasDisabled
。
如果新节点disalbed
为true
旧节点disabled
为false
,就把子节点移动到原本的位置。
反之则是,新节点disalbed
为false
,旧节点disabled
为true
,就把子节点移动到目标位置。
Teleport的删除
Teleport
的删除和同样是内置组件的keep-alive
是不同的,keep-alive
是将组件搬运到一个隐藏的容器里面藏起来。Teleport
的删除是通过遍历 teleprot
的子节点进行 unmount
删除。
总结
Teleport
是一个蛮不错的内置组件,Teleport
只改变了渲染的 DOM 结构,它不会影响组件间的逻辑关系。也就是说,如果 Teleport
包含了一个组件,那么该组件始终和这个使用了 teleport
的组件保持逻辑上的父子关系。传入的 props 和触发的事件也会照常工作。
这也意味着来自父组件的注入也会按预期工作,子组件将在 Vue Devtools 中嵌套在父级组件下面,而不是放在实际内容移动到的地方。
甚至而言我们也可以将 Teleport
和 Transition
结合使用来创建一个带动画的模态框。这样子的话,就可以轻易的实现很多动画效果。
转载自:https://juejin.cn/post/7366185126801883171