Teleport 看標題真的不知道是什麼?
原來我對網路的知識還是蠻缺乏的~
繼續看下去www
<Teleport>
是一個內建組件,允許我們將組件模板的一部分「傳送」到存在於該組件 DOM 階層之外的 DOM 節點中。
有時我們可能會遇到以下情況:組件模板的一部分在邏輯上屬於該組件,但從視覺的角度來看,應該顯示在 DOM 中的其他位置,超出 Vue 應用程序的範疇。
最常見的例子是構建全屏模式對話框。理想情況下,我們希望模式對話框的按鈕和模式本身在同一組件內,因為它們都與模式的開/關狀態相關。但這意味著模式將與按鈕一起渲染,並深深嵌套在應用程序的 DOM 層次中。這在通過 CSS 定位模式時可能會產生一些棘手的問題。
考慮以下 HTML 結構。
<div class="outer">
<h3>Vue Teleport Example</h3>
<div>
<MyModal />
</div>
</div>
以下是 <MyModal> 的實現:
<script setup>
import { ref } from 'vue'
const open = ref(false)
</script>
<template>
<button @click="open = true">Open Modal</button>
<div v-if="open" class="modal">
<p>Hello from the modal!</p>
<button @click="open = false">Close</button>
</div>
</template>
<style scoped>
.modal {
position: fixed;
z-index: 999;
top: 20%;
left: 50%;
width: 300px;
margin-left: -150px;
}
</style>
該組件包含一個按鈕來觸發模式的開啟,以及一個 class 為 .modal 的 div,該 div 將包含模式的內容和一個用於自我關閉的按鈕。
在初始 HTML 結構中使用這個組件時,有許多潛在問題:
position: fixed
只會將元素相對於視口放置,當沒有祖先元素設置了 transform、perspective 或 filter 屬性時。如果我們打算用 CSS 變換動畫祖先 <div class="outer">
,將會破壞模式的佈局!<div class="outer">
重疊並且具有更高的 z-index,則會覆蓋我們的模式。<Teleport>
提供了一種乾淨的解決方法,通過允許我們脫離嵌套的 DOM 結構來解決這些問題。讓我們修改 <MyModal>
使用 <Teleport>
:
<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>
<Teleport>
的 to 目標預期是一個 CSS 選擇器字符串或實際的 DOM 節點。在這裡,我們基本上是在告訴 Vue「將這個模板片段傳送到 body 標籤」。
您可以點擊下面的按鈕並通過瀏覽器的開發者工具檢查 <body>
標籤:
您可以將 <Teleport>
與 <Transition>
結合使用以創建動畫模式 - 參見此範例。
傳送的目標必須在 <Teleport>
組件掛載時已經存在於 DOM 中。理想情況下,這應該是整個 Vue 應用程序之外的元素。如果目標是由 Vue 渲染的其他元素,您需要確保該元素在 <Teleport>
之前已經掛載。
<Teleport>
僅改變渲染的 DOM 結構,不會影響組件的邏輯層次。也就是說,如果 <Teleport>
包含一個組件,該組件將仍然是包含 <Teleport>
的父組件的邏輯子組件。屬性傳遞和事件發射將繼續以相同的方式運作。
這也意味著來自父組件的注入將按預期工作,並且子組件將在 Vue Devtools 中嵌套在父組件之下,而不是被放置在實際內容移動到的地方。
在某些情況下,我們可能希望有條件地禁用 <Teleport>
。例如,我們可能希望在桌面上渲染一個組件作為覆蓋層,但在移動設備上以內嵌的方式呈現。<Teleport>
支援 disabled
屬性,可以動態切換:
<template>
<Teleport :disabled="isMobile">
...
</Teleport>
</template>
其中 isMobile
狀態可以通過檢測媒體查詢的變化來動態更新。
一個常見的用例是可重用的 <Modal>
組件,可能同時有多個實例活躍。在這種情況下,多個 <Teleport>
組件可以將它們的內容掛載到相同的目標元素上。這樣的順序將是簡單的附加 - 後面掛載的內容將位於目標元素內的前面內容之後。
以下是用法示例:
<template>
<Teleport to="#modals">
<div>A</div>
</Teleport>
<Teleport to="#modals">
<div>B</div>
</Teleport>
</template>
渲染結果將為:
<div id="modals">
<div>A</div>
<div>B</div>
</div>
在 Vue 3.5 及以上版本中,我們可以使用 defer
屬性來延遲 Teleport 的目標解析,直到應用程序的其他部分已經掛載。這樣可以使 Teleport 目標指向由 Vue 渲染的容器元素,但位於組件樹的後面部分:
<template>
<Teleport defer to="#late-div">...</Teleport>
<!-- 在模板的稍後部分 -->
<div id="late-div"></div>
</template>
請注意,目標元素必須在與 Teleport 相同的掛載/更新循環中渲染 - 也就是說,如果 <div>
只在一秒後才掛載,Teleport 仍然會報告錯誤。defer
的工作方式類似於掛載生命週期鉤子。
<Teleport>
是 Vue.js 提供的一個內建組件,主要用於將組件的某部分渲染到 DOM 中的不同位置。這意味著即使某個元素邏輯上屬於某個組件,它也可以在視覺上被放置在 DOM 的其他位置。
<Teleport>
可以避免層疊上下文(stacking context)造成的問題。例如,模態框通常需要有一個較高的 z-index
值來覆蓋其他內容,如果將模態框放在原本的組件中,可能會因為 z-index
被限制而無法正確顯示。使用 <Teleport>
可以將其渲染到 <body>
中,解決這個問題。position: fixed
和 position: absolute
定位可能會受到其父元素樣式的影響,使用 <Teleport>
可以使這些元素獨立於其邏輯結構,直接放到頁面的合適位置,避免層疊和定位的問題。假設你有一個按鈕來打開模態框,你希望這個模態框在頁面最上層顯示,而不是在原本的按鈕旁邊。你可以這樣寫:
<template>
<button @click="open = true">打開模態框</button>
<Teleport to="body">
<div v-if="open" class="modal">
<p>這是模態框內容</p>
<button @click="open = false">關閉</button>
</div>
</Teleport>
</template>
在這個例子中,模態框的內容會被「瞬移」到 <body>
中,這樣它就不會受到任何父元素的影響,可以自由地用 CSS 定位並且不會被其他元素遮擋。
對於 DOM 的不同位置,指的是在 HTML 結構中元素的層級和順序。在 Web 開發中,DOM(文檔物件模型)是用來表示網頁結構的樹狀結構,所有的 HTML 元素都是這個樹的節點。每個節點都有其父節點和子節點,這決定了它們的層級關係。
<div>
內的 <p>
標籤,其位置就被限制在這個 <div>
中。<Teleport>
的用途。假設你有以下 HTML 結構:
<div id="app">
<h1>我的網站</h1>
<button id="openModal">打開模態框</button>
</div>
<h1>
和 <button>
都是 <div id="app">
的子元素。<Teleport>
,你可以將模態框的內容渲染到 <body>
中,而不是在 <div id="app">
內部。這樣模態框就不會受到父元素的樣式影響,並且可以正確顯示在頁面最上層。<Teleport to="body">
<div class="modal">
<p>這是模態框內容</p>
<button @click="closeModal">關閉</button>
</div>
</Teleport>
在這個例子中,模態框將會被渲染到 <body>
標籤下,而不是在原本的 <div id="app">
結構內。這樣做可以解決層疊上下文和 z-index 的問題,使模態框能夠正確顯示。