vue3中Teleport的用法以及使用场景

1. 基本概念

Teleport 是 Vue3 提供的一个内置组件,它可以将组件的内容传送到 DOM 树的任何位置,而不受组件层级的限制。这在处理模态框、通知、弹出菜单等需要突破组件层级限制的场景中特别有用。

1.1 基本语法

vue 复制代码
<template>
  <teleport to="body">
    <!-- 这里的内容会被传送到 body 标签下 -->
    <div class="modal">
      <!-- 模态框内容 -->
    </div>
  </teleport>
</template>

2. 常见使用场景

2.1 模态框

vue 复制代码
<!-- Modal.vue -->
<template>
  <teleport to="body">
    <div v-if="isOpen" class="modal-overlay">
      <div class="modal">
        <div class="modal-header">
          <h3>{{ title }}</h3>
          <button @click="close">×</button>
        </div>
        <div class="modal-body">
          <slot></slot>
        </div>
        <div class="modal-footer">
          <slot name="footer">
            <button @click="close">关闭</button>
          </slot>
        </div>
      </div>
    </div>
  </teleport>
</template>

<script setup>
const props = defineProps({
  isOpen: Boolean,
  title: String
})

const emit = defineEmits(['update:isOpen'])

const close = () => {
  emit('update:isOpen', false)
}
</script>

<style scoped>
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.modal {
  background: white;
  padding: 20px;
  border-radius: 8px;
  min-width: 300px;
}
</style>

2.2 通知提示

vue 复制代码
<!-- Notification.vue -->
<template>
  <teleport to="#notifications-container">
    <div
      v-if="show"
      :class="['notification', type]"
      @click="close"
    >
      <div class="notification-content">
        {{ message }}
      </div>
    </div>
  </teleport>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const props = defineProps({
  message: String,
  type: {
    type: String,
    default: 'info'
  },
  duration: {
    type: Number,
    default: 3000
  }
})

const show = ref(true)

const close = () => {
  show.value = false
}

onMounted(() => {
  if (props.duration > 0) {
    setTimeout(close, props.duration)
  }
})
</script>

<style scoped>
.notification {
  position: fixed;
  top: 16px;
  right: 16px;
  padding: 12px 24px;
  border-radius: 4px;
  cursor: pointer;
  transition: all 0.3s;
}

.info {
  background: #e6f7ff;
  border: 1px solid #91d5ff;
}

.success {
  background: #f6ffed;
  border: 1px solid #b7eb8f;
}

.error {
  background: #fff2f0;
  border: 1px solid #ffccc7;
}
</style>

2.3 上下文菜单

vue 复制代码
<!-- ContextMenu.vue -->
<template>
  <teleport to="body">
    <div
      v-if="show"
      class="context-menu"
      :style="position"
    >
      <slot></slot>
    </div>
  </teleport>
</template>

<script setup>
import { ref, computed } from 'vue'

const props = defineProps({
  show: Boolean,
  x: Number,
  y: Number
})

const position = computed(() => ({
  left: props.x + 'px',
  top: props.y + 'px'
}))
</script>

<style scoped>
.context-menu {
  position: fixed;
  background: white;
  border: 1px solid #eee;
  border-radius: 4px;
  padding: 8px 0;
  min-width: 160px;
  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
}
</style>

3. 高级用法

3.1 动态目标

vue 复制代码
<template>
  <teleport :to="target">
    <div class="content">
      动态传送的内容
    </div>
  </teleport>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const target = ref('body')

onMounted(() => {
  // 可以根据条件动态改变目标
  if (window.innerWidth < 768) {
    target.value = '#mobile-container'
  }
})
</script>

3.2 多个 Teleport 到同一目标

vue 复制代码
<template>
  <teleport to="#notifications">
    <div class="notification">通知 1</div>
  </teleport>
  
  <teleport to="#notifications">
    <div class="notification">通知 2</div>
  </teleport>
</template>

3.3 条件性传送

vue 复制代码
<template>
  <teleport to="body" :disabled="isMobile">
    <div class="modal">
      <!-- 在移动端不会被传送,保持原位置 -->
    </div>
  </teleport>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const isMobile = ref(false)

onMounted(() => {
  isMobile.value = window.innerWidth < 768
  
  window.addEventListener('resize', () => {
    isMobile.value = window.innerWidth < 768
  })
})
</script>

4. 实际应用示例

4.1 全局加载指示器

vue 复制代码
<!-- LoadingIndicator.vue -->
<template>
  <teleport to="body">
    <div v-if="loading" class="loading-overlay">
      <div class="loading-spinner"></div>
      <div class="loading-text">{{ message }}</div>
    </div>
  </teleport>
</template>

<script setup>
defineProps({
  loading: Boolean,
  message: {
    type: String,
    default: '加载中...'
  }
})
</script>

<style scoped>
.loading-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(255, 255, 255, 0.9);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  z-index: 9999;
}
</style>

4.2 图片预览器

vue 复制代码
<!-- ImageViewer.vue -->
<template>
  <teleport to="body">
    <div
      v-if="visible"
      class="image-viewer"
      @click="close"
    >
      <img
        :src="imageUrl"
        @click.stop
      >
      <div class="controls">
        <button @click.stop="prev">&lt;</button>
        <button @click.stop="next">&gt;</button>
      </div>
    </div>
  </teleport>
</template>

<script setup>
const props = defineProps({
  visible: Boolean,
  imageUrl: String,
  images: Array
})

const emit = defineEmits(['update:visible'])

const close = () => {
  emit('update:visible', false)
}

const prev = () => {
  // 实现上一张逻辑
}

const next = () => {
  // 实现下一张逻辑
}
</script>

5. 最佳实践

5.1 目标元素管理

html 复制代码
<!-- index.html -->
<!DOCTYPE html>
<html>
<head>
  <title>Vue App</title>
</head>
<body>
  <div id="app"></div>
  
  <!-- 为 Teleport 预留的容器 -->
  <div id="modals"></div>
  <div id="notifications"></div>
  <div id="tooltips"></div>
</body>
</html>

5.2 组件封装

vue 复制代码
<!-- BaseModal.vue -->
<template>
  <teleport to="#modals">
    <transition name="modal">
      <div
        v-if="modelValue"
        class="modal-container"
        @click.self="close"
      >
        <div class="modal-content">
          <slot></slot>
        </div>
      </div>
    </transition>
  </teleport>
</template>

<script setup>
defineProps({
  modelValue: Boolean
})

const emit = defineEmits(['update:modelValue'])

const close = () => {
  emit('update:modelValue', false)
}
</script>

6. 注意事项

  1. 目标元素存在性检查
vue 复制代码
<template>
  <teleport to="#target" :disabled="!targetExists">
    <div>内容</div>
  </teleport>
</template>

<script setup>
import { ref, onMounted } from 'vue'

const targetExists = ref(false)

onMounted(() => {
  targetExists.value = !!document.querySelector('#target')
})
</script>
  1. SSR 兼容性
vue 复制代码
<template>
  <client-only>
    <teleport to="body">
      <div>仅客户端渲染的内容</div>
    </teleport>
  </client-only>
</template>
  1. 清理工作
vue 复制代码
<script setup>
import { onUnmounted } from 'vue'

onUnmounted(() => {
  // 确保清理所有传送的内容
  const target = document.querySelector('#target')
  if (target) {
    target.innerHTML = ''
  }
})
</script>
相关推荐
Dontla15 分钟前
为什么React列表项需要key?(React key)(稳定的唯一标识key有助于React虚拟DOM优化重绘大型列表)
javascript·react.js·ecmascript
EndingCoder1 小时前
React从基础入门到高级实战:React 实战项目 - 项目三:实时聊天应用
前端·react.js·架构·前端框架
阿阳微客2 小时前
Steam 搬砖项目深度拆解:从抵触到真香的转型之路
前端·笔记·学习·游戏
德育处主任Pro3 小时前
『React』Fragment的用法及简写形式
前端·javascript·react.js
CodeBlossom3 小时前
javaweb -html -CSS
前端·javascript·html
CodeCraft Studio3 小时前
【案例分享】如何借助JS UI组件库DHTMLX Suite构建高效物联网IIoT平台
javascript·物联网·ui
打小就很皮...4 小时前
HBuilder 发行Android(apk包)全流程指南
前端·javascript·微信小程序
集成显卡5 小时前
PlayWright | 初识微软出品的 WEB 应用自动化测试框架
前端·chrome·测试工具·microsoft·自动化·edge浏览器
前端小趴菜056 小时前
React - 组件通信
前端·react.js·前端框架
Amy_cx6 小时前
在表单输入框按回车页面刷新的问题
前端·elementui