Vue 3 Teleport 元件:跨越 DOM 層級的渲染技巧
Vue 3 Teleport 元件:跨越 DOM 層級的渲染技巧
在開發複雜的 UI 元件時,你有沒有遇過這樣的困境:需要在深層的子元件中顯示一個 Modal 或 Tooltip,但因為父層的 overflow: hidden 或 z-index 造成顯示異常?這是一個非常經典的前端問題。
Vue 3 引入的 <Teleport> 元件正是為了解決這個問題而生。它允許你將元件的模板內容「傳送」到 DOM 樹的其他位置,同時保留 Vue 的響應式綁定和元件邏輯。
什麼是 Teleport?
<Teleport> 是一個內建元件,讓你可以指定一個目標 DOM 節點,將插槽內容渲染到那個位置,而不是元件本身的位置。
最常見的使用場景:
- Modal / Dialog 對話框
- Tooltip 提示框
- Toast 通知
- 下拉選單(Dropdown)
- 全螢幕覆蓋層
基本用法
假設你有以下的 HTML 結構:
<!-- index.html -->
<body>
<div id="app"></div>
<div id="modals"></div> <!-- Modal 的目標位置 -->
</body>在元件中使用 Teleport:
<template>
<div class="product-card">
<h2>{{ product.name }}</h2>
<button @click="showModal = true">查看詳情</button>
<!-- 這段內容會被渲染到 #modals,而不是 .product-card 裡面 -->
<Teleport to="#modals">
<div v-if="showModal" class="modal-overlay" @click.self="showModal = false">
<div class="modal-content">
<h3>{{ product.name }}</h3>
<p>{{ product.description }}</p>
<button @click="showModal = false">關閉</button>
</div>
</div>
</Teleport>
</div>
</template>
<script setup>
import { ref } from 'vue'
const props = defineProps(['product'])
const showModal = ref(false)
</script>搭配 Transition 使用
Teleport 可以與 <Transition> 完美配合,實現平滑的動畫效果:
<template>
<Teleport to="body">
<Transition name="modal">
<div v-if="isVisible" class="modal-overlay">
<div class="modal-box">
<slot />
</div>
</div>
</Transition>
</Teleport>
</template>
<style scoped>
.modal-enter-active,
.modal-leave-active {
transition: opacity 0.3s ease;
}
.modal-enter-from,
.modal-leave-to {
opacity: 0;
}
.modal-enter-active .modal-box,
.modal-leave-active .modal-box {
transition: transform 0.3s ease;
}
.modal-enter-from .modal-box,
.modal-leave-to .modal-box {
transform: translateY(-20px) scale(0.95);
}
</style>實戰:打造可複用的 Modal 元件
讓我們建立一個完整的 Modal 元件:
<!-- components/BaseModal.vue -->
<template>
<Teleport to="body">
<Transition name="modal-fade">
<div
v-if="modelValue"
class="modal-backdrop"
@click.self="$emit('update:modelValue', false)"
>
<div
class="modal-container"
:style="{ maxWidth: width }"
role="dialog"
:aria-label="title"
>
<header class="modal-header">
<h2 class="modal-title">{{ title }}</h2>
<button
class="modal-close"
@click="$emit('update:modelValue', false)"
aria-label="關閉"
>
✕
</button>
</header>
<div class="modal-body">
<slot />
</div>
<footer v-if="$slots.footer" class="modal-footer">
<slot name="footer" />
</footer>
</div>
</div>
</Transition>
</Teleport>
</template>
<script setup>
defineProps({
modelValue: {
type: Boolean,
required: true
},
title: {
type: String,
default: ''
},
width: {
type: String,
default: '500px'
}
})
defineEmits(['update:modelValue'])
</script>使用這個元件:
<template>
<div>
<button @click="isOpen = true">開啟設定</button>
<BaseModal v-model="isOpen" title="帳號設定" width="600px">
<form @submit.prevent="saveSettings">
<input v-model="username" placeholder="使用者名稱" />
<input v-model="email" type="email" placeholder="電子郵件" />
</form>
<template #footer>
<button @click="isOpen = false">取消</button>
<button @click="saveSettings">儲存</button>
</template>
</BaseModal>
</div>
</template>
<script setup>
import { ref } from 'vue'
import BaseModal from './BaseModal.vue'
const isOpen = ref(false)
const username = ref('')
const email = ref('')
function saveSettings() {
// 儲存邏輯
isOpen.value = false
}
</script>disabled 屬性:條件式傳送
有時候你可能需要根據條件決定是否啟用 Teleport:
<Teleport to="#modal-target" :disabled="isMobile">
<div class="panel">
<!-- 在桌面版傳送到 #modal-target,在手機版留在原地 -->
<slot />
</div>
</Teleport>多個 Teleport 到同一目標
如果多個 Teleport 指向同一個目標節點,內容會依序附加:
<!-- 第一個元件 -->
<Teleport to="#notifications">
<div class="notification">訊息 A</div>
</Teleport>
<!-- 第二個元件 -->
<Teleport to="#notifications">
<div class="notification">訊息 B</div>
</Teleport>結果會是兩個通知依序出現在 #notifications 中。
注意事項
目標必須存在:Teleport 的目標 DOM 元素必須在 Vue 掛載前就存在,或至少在 Teleport 渲染時存在。
CSS 作用域:Teleport 的內容雖然在不同的 DOM 位置,但 scoped CSS 仍然有效,因為作用域是編譯時決定的。
父子關係保持:儘管內容被傳送到其他 DOM 位置,它在邏輯上仍然是原始元件的子元件,可以正常使用 inject/provide。
伺服器端渲染(SSR):在 SSR 環境中,Teleport 需要特別處理,建議使用
defer屬性或在onMounted後才啟用。
總結
<Teleport> 優雅地解決了長久以來困擾前端開發者的 DOM 層級問題。它讓你可以在保持元件邏輯完整性的同時,自由控制內容的渲染位置。對於 Modal、Toast、Tooltip 等需要脫離文件流的 UI 元件來說,Teleport 是最佳解決方案之一。善用這個特性,你的 Vue 應用會更加健壯和易於維護。
分享這篇文章