六维教程

Vue3 Teleport

平时组件模板里写的东西,最终会挂到它所在的位置,但弹窗、菜单、提示这一类的元素常有问题,它们会被父级的 overflow: hiddentransformz-index 卡住,该冒出来的冒不出来。

Teleport(传送到) 能把一段模板”传送到”页面上任意一个位置去挂载,而这段模板对应的组件逻辑还留在原组件里,弹窗、浮层几乎都要靠它,是现代组件不可或缺的一招。

为什么需要它

先看问题,弹窗写在组件里的某个角落:

<div class="card" style="overflow: hidden">
  <button @click="show = true">打开弹窗</button>
  <!-- 弹窗实际会跟着 card 一起被裁掉 -->
  <div v-if="show" class="modal">...</div>
</div>

overflow: hidden 一设置,弹窗超出容器部分就被切掉了,浏览器顶部 position: fixed 也救不回来。

归根结底一句话,元素渲染在哪,就受哪的层层约束,想摆脱约束,就得让它渲染到别处,这就是 Teleport 的用武之地。

基本用法:<Teleport to="目标">

to 填一个 CSS 选择器,Vue 会把里面的内容挂到那个元素上,最常用的是 body

<template>
  <Teleport to="body">
    <div class="modal">
      <p>弹窗内容</p>
      <button @click="close">关闭</button>
    </div>
  </Teleport>
</template>

<script setup>
const show = ref(false)
</script>

使用前后对比:

  • 不使用 Teleport,.modal 渲染在组件当前位置,受父容器约束。
  • 使用后,.modal 的 DOM 直接进 body,页面最外层,不再被任何容器裁剪。

to 也可以指向其他选择器,比如 to="#app"to=".container",只要是页面上存在的元素即可。

目标元素找不到会怎样?

会退回在原位置渲染,同时在控制台给一条警告,所以别把 to 写成不存在的类名,弹窗位置会和预期不符。

逻辑归属不变

DOM 被传送走了,但组件逻辑没有跟着走,props、emits、作用域插槽这些依然属于写 Teleport 的组件,使用体验和普通组件一致:

  • 父组件传的 props 照常在 <Teleport> 里生效。
  • 模板里的 v-ifv-for 照常判断。
  • scoped 样式也照常生效,.modal 的类名还能被子级穿透选择器命中。

原生事件冒泡会变吗

会。DOM 结构变了,原生事件按新的 DOM 树冒泡,而不是回到子组件里,如果监听了 document 上的点击来”点空白关闭弹窗”,要留意事件源的判断。

条件使用:disabled

disabled 为真时关闭传送到,内容退回原位渲染,适合小屏设备把弹层降级成页内模块:

<Teleport :disabled="isMobile">
  <div class="drawer">...</div>
</Teleport>

组合套路:Teleport + Transition

Teleport 管挂哪,Transition(Vue3 过渡动画)管出现和离开的动画,两者互不干扰,正好叠着用。弹窗淡入淡出再加”挂到 body”是最常见的写法:

<Teleport to="body">
  <Transition>
    <!-- 淡入淡出,挂载在 body 下 -->
    <div v-if="show" class="modal">...</div>
  </Transition>
</Teleport>

挂到 body 后,弹窗和页面其他元素处于同一层,这时层级冲突就要靠 z-index 和遮罩层解决,别忘记加一个半透明背景用于点击关闭。

速查卡片

场景 写法
弹窗浮层挂外层 <Teleport to="body">
挂到其他元素 to="#app" 或任意选择器
临时退回原位 :disabled="条件"
淡入淡出弹窗 <Teleport> 里包 <Transition>
目标不存在 退回原位置并给警告,稳步排查即可
上一篇
Vue3 过渡动画
下一篇
Vue3 路由基础