浅析Vue3新特性-Teleport(任意传送门也称瞬间移动):为什么需要Teleport、与React的Portals特性、如何使用(直接使用、与组件搭配使用、使用多个teleport)、teleport API(to及disabled使用介绍)
Teleport
是什么?它解决的是什么问题?
一、使用场景 - 为什么我们需要 Teleport
Teleport
是一种能够将我们的模板移动到 DOM
中 Vue app
之外的其他位置的技术。
1、使用场景:
业务开发的过程中,我们经常会封装一些常用的组件,例如 Modal 组件。相信大家在使用 Modal 组件的过程中,经常会遇到一个问题,那就是 Modal 的定位问题。
(1)像 modals
,toast
等这样的元素,很多情况下,我们将它完全的和我们的 Vue
应用的 DOM
完全剥离,管理起来反而会方便容易很多。原因在于如果我们嵌套在 Vue
的某个组件内部,那么处理嵌套组件的定位、z-index
和样式就会变得很困难。
(2)另外,像 modals
、toast
等这样的元素需要使用到 Vue
组件的状态(data
或者 props
)的值。
2、作用 - 这就是 Teleport
派上用场的地方:
我们可以在组件的逻辑位置写模板代码,这意味着我们可以使用组件的 data
或 props
,然后在 Vue
应用的范围之外渲染它。
Teleport 提供了一种干净的方法,允许我们控制在 DOM 中哪个父节点下呈现 HTML,而不必求助于全局状态或将其拆分为两个组件。 -- Vue 官方文档
<teleport>
组件可以将其内容添加到同一目标元素。
<teleport to="#modals">
<div>A</div>
</teleport>
<teleport to="#modals">
<div>B</div>
</teleport>
// result
<div id="modals">
<div>A</div>
<div>B</div>
</div>
二、React 的 Portals 特性
介绍了 Teleport 之后我们也来了解一下 React 的 Portals 特性。Portal 提供了一种将子节点渲染到存在于父组件以外的 DOM 节点的优秀的方案。
ReactDOM.createPortal(child, container)
第一个参数(child)是任何可渲染的 React 子元素,例如一个元素,字符串或 fragment。第二个参数(container)是一个 DOM 元素。
render() {
// React 挂载了一个新的 div,并且把子元素渲染其中
return (
<div>
{this.props.children}
</div>
);
}
然而,有时候将子元素插入到 DOM 节点中的不同位置也是有好处的:
render() {
// React 并没有创建一个新的 div。它只是把子元素渲染到 `domNode` 中。
// `domNode` 是一个可以在任何位置的有效 DOM 节点。
return ReactDOM.createPortal(
this.props.children,
domNode
);
}
一个 portal 的典型用例是当父组件有 overflow: hidden 或 z-index 样式时,但你需要子组件能够在视觉上“跳出”其容器。例如,对话框、悬浮卡以及提示框。常见的情况是创建一个包含全屏模式的组件。
对话框 position: absolute 的定位相对于父 div 作为参考。Teleport 提供了一种简单的方法,使我们可以控制要在DOM中哪个父对象下呈现HTML。
三、Teleport 使用
1、代码示例:直接使用
// 1、index.html 中,我们加一个和 app 同级的 div
<div id="app"></div>
<div id="teleport-target"></div>
// 2、HelloWorld.vue 中,添加如下,留意 to 属性跟上面的 id 选择器一致
<button @click="showToast" class="btn">打开 toast</button>
// to 属性就是目标位置
<teleport to="#teleport-target">
<div v-if="visible" class="toast-wrap">
<div class="toast-msg">我是一个 Toast 文案</div>
</div>
</teleport>
// 3、再使用 js 我们使用组件里的 变量 去控制 toast 显示隐藏
import { ref } from 'vue';
export default {
setup() {
// toast 的封装
const visible = ref(false);
let timer;
const showToast = () => {
visible.value = true;
clearTimeout(timer);
timer = setTimeout(() => {
visible.value = false;
}, 2000);
}
return {
visible,
showToast
}
}
}
可以看到,我们使用 teleport
组件:
(1)通过 to
属性,指定该组件渲染的位置与 <div id="app"></div>
同级,也就是在 body
下,
teleport
的状态 visible
又是完全由内部 Vue
组件控制2、代码示例:与 Vue 组件一起使用 - modal
如果 <teleport>
包含 Vue
组件,则它仍将是 <teleport>
父组件的逻辑子组件
<teleport to="#modal-container">
// use the modal component, pass in the prop
<modal :show="showModal" @close="showModal = false">
<template #header>
<h3>custom header</h3>
</template>
</modal>
</teleport>
import { ref } from 'vue';
import Modal from './Modal.vue';
export default {
components: {
Modal
},
setup() {
// modal 的封装
const showModal = ref(false);
return {
showModal
}
}
}
在这种情况下,即使在不同的地方渲染 Modal
,它仍将是当前组件(调用 Modal
的组件)的子级,并将从中接收 prop,这也意味着来自父组件的注入按预期工作,并且子组件将嵌套在
Vue Devtools
中的父组件之下,而不是放在实际内容移动到的位置。
3、在同一目标上使用多个teleport
一个常见的用例场景是一个可重用的<Modal>组件,它可能同时有多个实例处于活动状态。
四、Teleport API
1、to - string,需要prop,必须是有效的查询选择器或HTMLElement(如果在浏览器环境中使用)。指定将在其中一栋<teleport>内容的目标元素
2、disabled - boolean,此可选属性可用于禁用<teleport>的功能,这意味着其插槽内容将不会移动到任何位置,而是在您在周围父组件中指定了<teleport>的位置渲染。
<teleport to="#popup" :disabled="displayVideoInline">
<video src="./my-movie.mp4">
</teleport>
请注意,这将移动实际的DOM节点,而不是被销毁和重新创建,并且它还将保持任何组件实例的活动状态。所有有状态的HTML元素(即播放的视频)都将保持其状态。