<Teleport>
是 Vue3 的內置組件,用以將 DOM 內容傳遞到指定的地方,
而不受限於某個父元素底下,無法使用相關功能。
舉一個小小例子:
▲帶有呼叫 dialog
按鈕的組件
當你在頁面內部有個按鈕,按了後會顯示 dialog
並在背景底部有遮罩,
那就會有個問題:
按鈕在頁面組件深處,
但dialog
和遮罩的z-index
應高於所有元素,
卻受限於父元素內無法輕易達成操作。
▲遮罩範圍應該是整個螢幕,而不是父容器的寬高範圍
<script setup>
import MyCard from "./components/Card.vue";
</script>
<template>
<div class="page">
<div class="content">
<MyCard />
</div>
</div>
</template>
▲App.vue
<script setup lang="ts">
import { ref } from "vue";
const showDialog = ref(false);
function toggleDialog() {
showDialog.value = !showDialog.value;
}
</script>
<template>
<div class="container">
<button @click="toggleDialog">開啟 Dialog</button>
<div v-if="showDialog">
<div class="overlay" @click="toggleDialog"></div>
<div class="dialog">
<p>這是一個 Dialog</p>
<button @click="toggleDialog">關閉</button>
</div>
</div>
</div>
</template>
<style scoped>
.container {
position: relative;
z-index: 0;
/* 其餘 style */
}
.overlay {
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
/* 其餘 style */
}
.dialog {
position: absolute;
z-index: 101;
/* 其餘 style */
}
</style>
▲MyCard.vue
▲遮罩實際內容大小
其實這邊 overlay 是超出內容元素的,
但因為人在屋簷下,不得不低頭,
沒辦法展現原本的樣子。
那我們這邊就來利用 <Teleport>
來輕鬆解決這個問題
<template>
<div class="container">
<button @click="toggleDialog">開啟 Dialog</button>
<!-- <Teleport>是內置元件 不用引入 -->
<Teleport defer to="body">
<div v-if="showDialog">
<div class="overlay" @click="toggleDialog"></div>
<div class="dialog">
<p>這是一個 Dialog</p>
<button @click="toggleDialog">關閉</button>
</div>
</div>
</Teleport>
</div>
</template>
▲MyCard.vue
這邊有幾個屬性可以來講一下使用方法 :
to
: 用來指定傳遞到哪個元素底下, 你可以有幾種指定方式'body'
=> <body>
.container
=><div class="container"></div>
#main-content
=> <div id="main-content"></div>
defer
: vue3.5+
<Teleport>
掛載時,傳送的to
目標必須已經存在於 DOM 中。理想情況下,
這應該是整個 Vue 應用 DOM > 樹外部的一個元素。如果目標元素也是由 Vue 渲染的,
你需要確保在掛載<Teleport>
之前先掛載該元素。
// node_modules>@vue>runtime-dow>dist>runtime-dom.esm-browser.js
const TeleportImpl = {
name: "Teleport",
__isTeleport: true,
process(n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, internals) {
// 省略部分程式碼
if (n1 == null) {
// 省略部分程式碼
if (isTeleportDeferred(n2.props)) {
n2.el.__isMounted = false; // 暫時標記 Teleport 的根節點未掛載,延遲處理
queuePostRenderEffect(() => {
mountToTarget(); // 待目標元件掛載
delete n2.el.__isMounted; // 刪除剛剛用來延遲掛載的暫時標記
}, parentSuspense);
} else {
mountToTarget();
}
▲defer 屬性
可能在某些情況,例如手機板你不希望顯示 dialog
,而是有其他處理方式,
那你也可以在這種情況下停用 Teleport。
<Teleport :disabled="isMobile">
...
</Teleport>
請避免在 SSR 的同時把 Teleport 的目標設為 body——通常
<body>
會包含其他服務端渲染出來的內容,這會使得 Teleport 無法確定激活的正確起始位置。
推薦用一個獨立的只包含 teleport 的內容的容器,例如
<div id="teleported"></div>。
▲index.html
Teleport 的存在並不影響父子組件的邏輯關係,
該傳的參數跟事件照常觸發,只是更改 DOM 元素的渲染結構。
明天就是最後一篇了!
<teleport>
的元素本身帶有 position:absolute
,那傳遞到<body>
可能會有什麼問題?