浅析Vue3新特性-Teleport(任意传送门也称瞬间移动):为什么需要Teleport、与React的Portals特性、如何使用(直接使用、与组件搭配使用、使用多个teleport)、teleport API(to及disabled使用介绍)

  Teleport 是什么?它解决的是什么问题?

一、使用场景 - 为什么我们需要 Teleport

  Teleport 是一种能够将我们的模板移动到 DOMVue app 之外的其他位置的技术。

1、使用场景:

  业务开发的过程中,我们经常会封装一些常用的组件,例如 Modal 组件。相信大家在使用 Modal 组件的过程中,经常会遇到一个问题,那就是 Modal 的定位问题。

(1)像 modalstoast 等这样的元素,很多情况下,我们将它完全的和我们的 Vue 应用的 DOM 完全剥离,管理起来反而会方便容易很多。原因在于如果我们嵌套在 Vue 的某个组件内部,那么处理嵌套组件的定位、z-index 和样式就会变得很困难。

(2)另外,像 modalstoast 等这样的元素需要使用到 Vue 组件的状态(data 或者 props)的值。

2、作用 - 这就是 Teleport 派上用场的地方:

  我们可以在组件的逻辑位置写模板代码,这意味着我们可以使用组件的 dataprops,然后在 Vue 应用的范围之外渲染它

Teleport 提供了一种干净的方法,允许我们控制在 DOM 中哪个父节点下呈现 HTML,而不必求助于全局状态或将其拆分为两个组件。 -- Vue 官方文档
3、多个 <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 方法返回一个元素时,该元素将被挂载到 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 下,

(2)但是 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元素(即播放的视频)都将保持其状态。

posted @ 2021-10-15 13:40  古兰精  阅读(1523)  评论(0编辑  收藏  举报