Vue 3 實戰:掌握 Teleport 傳送門,完美解決 Modal 彈窗與 CSS 層級限制
在開發複雜的前端介面時,我們經常會遇到 邏輯在內層,樣式在外層 的矛盾。最典型的範例就是 Modal(彈窗) 或 Tooltip(提示框)。
Vue 3 提供的 Teleport 元件,能讓您將元件的 HTML 結構 傳送 到 DOM 階層以外的指定位置(例如:<body> 標籤直屬下方),同時還能完整保留該元件內部的資料狀態與邏輯。
使用 Teleport 的理由
如果在一個深度嵌套的元件裡寫了一個全螢幕彈窗(Modal),會遇到以下麻煩:
- CSS 限制:如果外層元件設定了
overflow: hidden或z-index,彈窗可能會被遮擋或切掉。 - DOM 層級混亂:為了讓彈窗蓋住全螢幕,可能被迫把彈窗元件寫在
App.vue最外層,這會導致邏輯分散,按鈕在內層元件,彈窗卻在外層元件。
核心價值
Teleport 讓您實現了 邏輯封裝與視圖分離:您可以在內層元件編寫彈窗程式碼(方便管理邏輯),但最終渲染出來的 DOM 卻出現在 <body> 或您指定的任何地方。
基本用法:指定您的傳送目標
<teleport> 接收一個 to 屬性,用來指定目標容器。這個值可以是任何 CSS 選取器,例如:to="body"、to="#app-modals" 或是實際的 DOM 元素。
實戰範例:封裝一個不受限的 Modal
父元件呼叫方式
<!-- /views/Slot.vue -->
<script setup>
import Modal from "@/components/Modal.vue";
</script>
<template>
<Modal />
</template>Modal 元件內部實作
<!-- /components/Modal.vue -->
<script setup>
import { ref } from "vue";
const open = ref(false);
</script>
<template>
<button @click="open = true">Open Modal</button>
<teleport to="body">
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</teleport>
</template>
<style scoped>
.modal {
position: fixed;
z-index: 999;
top: 20%;
left: 50%;
width: 300px;
margin-left: -150px;
}
</style>Teleport 的關鍵特性
- 保留狀態:雖然 DOM 位置變了,但內部的
ref()、reactive()以及從父層接收的props依然維持響應式連動。 - 多個傳送門:多個
<teleport>可以同時指向同一個目標元素,如同一個to="body",Vue 會按照順序將它們依序掛載。 - Disabled 模式:您可以透過
:disabled="true"屬性暫時停用傳送功能,讓內容回到原本的位置。
<!-- 桌機把彈窗送到 body,手機版留在原地當內嵌區塊 -->
<teleport to="body" :disabled="isMobile">
<div class="modal">...</div>
</teleport>目標必須先存在
to 指定的元素必須在 Teleport 掛載時就已經存在於頁面上,否則會找不到目標而發出警告。若目標是由 Vue 自己渲染的,記得確認它比 Teleport 更早掛載。
總結
Teleport 是 Vue 3 中處理彈窗、通知與下拉選單的標準解答。它完美解決了 CSS 層級競爭問題,同時確保了元件開發的封裝性,讓您的 UI 架構更加穩健且易於調試。
常見問題
Teleport 是用來解決什麼問題的?
解決彈窗、提示框這類元件被祖先元素的 overflow: hidden 或 z-index 堆疊環境限制的問題。邏輯仍寫在內層元件,但實際 DOM 渲染到 <body> 或指定容器。
被傳送出去的內容還吃得到 scoped 樣式嗎?
可以。scoped 樣式是編譯期在元素上加屬性標記,與 DOM 最終位置無關,因此傳送後樣式依然有效。
傳送後元件的狀態會重置嗎?
不會。Teleport 只移動 DOM 節點的位置,元件實體本身沒有被銷毀,內部的 ref、reactive 與 props 都維持原本的響應式連動。
to 屬性可以指定哪些目標?
任何 CSS 選擇器字串或實際的 DOM 元素,例如:to="body" 或 to="#modals"。目標必須在 Teleport 掛載時就已存在於頁面上。
什麼時候會用到 disabled?
需要依情境切換渲染位置時,例如:桌機版把彈窗送到 <body>,手機版則留在原地當作內嵌區塊,只要動態綁定 disabled 即可。
多個 Teleport 指向同一個目標會怎麼排列?
會依照掛載的先後順序依序附加在目標容器內,後掛載的排在後面,不會互相覆蓋。