Skip to content

Vue 3 Teleport

tags: Vue

Teleport 是什麼?

Teleport 是 Vue 3 中的內建元件,它允許我們將模板內容傳送到 DOM 中的任何位置,而不受組件層級結構的限制,但同時保持原有的邏輯作用域

使用情境

  • 彈出對話框(Modal)
  • 提示框(Tooltip)
  • 通知系統(Notification)
  • 全螢幕覆蓋層
  • 固定位置的懸浮元素

注意事項

  • Teleport 不會改變組件的邏輯作用域
  • 傳送的內容仍然在原組件的上下文中
  • 目標選擇器必須存在於 DOM 中

如何使用?

vue
<template>
  <div>
    <button @click="openModal">開啟對話框</button>
    
    <teleport to="body">
      <div v-if="isModalOpen" class="modal">
        <h2>這是一個對話框</h2>
        <p>對話框的內容會被傳送到 body 標籤內</p>
        <button @click="closeModal">關閉</button>
      </div>
    </teleport>
  </div>
</template>

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

const isModalOpen = ref(false)

const openModal = () => {
  isModalOpen.value = true
}

const closeModal = () => {
  isModalOpen.value = false
}
</script>

用法

vue
<teleport to="目標選擇器">
  <!-- 要被傳送的內容 -->
</teleport>

雖然內容被傳送到其他位置,但仍然維持原本的組件作用域,包括:

  • 數據響應性
  • 事件處理
  • 組件狀態

範例

vue
<template>
  <div class="app">
    <h1>我的應用程式</h1>
    <button @click="showModal = true">開啟對話框</button>

    <teleport to="body">
      <div v-if="showModal" class="modal">
        <h2>對話框標題</h2>
        <p>這是一個被傳送到 body 的對話框</p>
        <button @click="showModal = false">關閉</button>
      </div>
    </teleport>
  </div>
</template>

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

const showModal = ref(false)
</script>

Teleport 特性

  • 靈活的目標位置
    • to 屬性可以接受任何有效的 CSS 選擇器
    • 常見的目標:body#app.modal-container
  • 保持原有邏輯作用域
    • 傳送的內容仍然可以使用原組件的數據和方法
  • 條件渲染
    • 可以與 v-if 結合使用
    • 支持動態控制是否傳送
  • 多重 Teleport
vue
<teleport to="body">
  <div>第一個傳送的元素</div>
</teleport>

<teleport to="body">
  <div>第二個傳送的元素</div>
</teleport>

渲染結果

<body>
  <div>第一個傳送的元素</div>
  <div>第二個傳送的元素</div>
</body>
  • disabled 屬性 (禁用) isMobile 狀態可以根據 CSS media query 的不同結果動態地更新
vue
<teleport to="body" :disabled="isMobile">
  <!-- 在移動設備上不進行傳送 -->
</teleport>
vue
<template>
  <teleport to="body" :disabled="isMobile">
    <div>
      根據螢幕大小決定是否使用 Teleport
    </div>
  </teleport>
</template>

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

const screenWidth = ref(window.innerWidth)

const isMobile = computed(() => screenWidth.value < 768)

// 監聽螢幕大小變化
window.addEventListener('resize', () => {
  screenWidth.value = window.innerWidth
})
</script>

進階用法:可重用模態框

vue
<!-- Modal.vue -->
<template>
  <teleport to="body">
    <div v-if="isOpen" class="modal-overlay">
      <div class="modal">
        <header>
          <slot name="header">預設標題</slot>
          <button @click="close">關閉</button>
        </header>
        <main>
          <slot>預設內容</slot>
        </main>
      </div>
    </div>
  </teleport>
</template>

<script setup>
import { defineProps, defineEmits } from 'vue'

const props = defineProps({
  isOpen: {
    type="boolean", 
    default: false
  }
})

const emit = defineEmits(['close'])

const close = () => {
  emit('close')
}
</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;
}

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

使用範例:

vue
<template>
  <div>
    <button @click="modalOpen = true">打開對話框</button>
    
    <Modal 
      :is-open="modalOpen"
      @close="modalOpen = false"
    >
      <template #header>
        <h2>自定義標題</h2>
      </template>
      
      <p>這是對話框的內容</p>
    </Modal>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Modal from './Modal.vue'

const modalOpen = ref(false)
</script>

瀏覽器支援

  • Vue 3 的 Teleport 在現代瀏覽器中有很好的支援
  • 舊版瀏覽器可能需要 polyfill

參考文件