跳轉到內容

Teleport

<Teleport> 是一個內建元件,允許我們將元件模板的一部分 "傳送" 到一個位於該元件DOM層次結構之外的DOM節點。

基本用法

有時我們可能會遇到以下場景:元件模板的一部分在邏輯上屬於它,但從視覺角度來看,它應該顯示在DOM中的其他位置,在Vue應用程式之外。

這種情況最常見的例子是在構建全屏模態時。理想情況下,我們希望模態的按鈕和模態本身位於同一個元件中,因為它們都與模態的開啟/關閉狀態相關。但這意味著模態將與按鈕一起渲染,深度巢狀在應用程式的DOM層次結構中。這可能會在透過CSS定位模態時產生一些棘手的問題。

考慮以下HTML結構。

template
<div class="outer">
  <h3>Vue Teleport Example</h3>
  <div>
    <MyModal />
  </div>
</div>

以下是 <MyModal> 的實現

vue
<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>
vue
<script>
export default {
  data() {
    return {
      open: 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>

該元件包含一個 <button> 用於觸發模態的開啟,一個帶有 .modal 類的 <div>,其中將包含模態的內容和一個按鈕來自動關閉。

當在初始HTML結構中使用此元件時,存在一些潛在問題

  • position: fixed 僅在沒有任何祖先元素設定 transformperspectivefilter 屬性時將元素定位到視口相對位置。例如,如果我們打算使用CSS轉換來動畫化祖先 <div class="outer">,這將破壞模態佈局!

  • 模態的 z-index 受其容器元素的約束。如果有其他元素與 <div class="outer"> 相重疊並具有更高的 z-index,它將覆蓋我們的模態。

<Teleport> 提供了一種簡潔的方法來解決這個問題,透過允許我們跳出巢狀的DOM結構。讓我們修改 <MyModal> 以使用 <Teleport>

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>

<Teleport>to 目標期望一個CSS選擇器字串或一個實際的DOM節點。在這裡,我們實際上是告訴Vue "傳送 這個模板片段到 body 標籤"。

您可以透過點選下面的按鈕並使用瀏覽器開發工具檢查 <body> 標籤

您可以將 <Teleport><Transition> 結合使用來建立動畫模態框 - 請參閱 此處示例

技巧

teleport 的 to 目標必須在 <Teleport> 元件掛載時已經在 DOM 中。理想情況下,這應該是一個整個 Vue 應用程式之外的外部元素。如果您要針對由 Vue 渲染的另一個元素,請確保在該元素掛載之前掛載 <Teleport>

與元件一起使用

<Teleport> 只會更改渲染的 DOM 結構 - 它不會影響元件的邏輯層次結構。也就是說,如果 <Teleport> 包含一個元件,那麼該元件將仍然是包含 <Teleport> 的父元件的邏輯子元件。屬性傳遞和事件發射將繼續以相同的方式工作。

這也意味著父元件的注入將按預期工作,並且子元件將在 Vue Devtools 中位於父元件下方,而不是實際內容移動到的位置。

停用 Teleport

在某些情況下,我們可能希望有條件地停用 <Teleport>。例如,我們可能希望將元件作為桌面上的覆蓋層渲染,但在移動端上內聯渲染。 <Teleport> 支援 disabled 屬性,可以動態切換

template
<Teleport :disabled="isMobile">
  ...
</Teleport>

其中 isMobile 狀態可以透過檢測媒體查詢更改來動態更新。

同一目標上的多個 Teleport

一個常見的用例是可重用的 <Modal> 元件,可能存在多個例項同時活躍。在這種情況下,多個 <Teleport> 元件可以將它們的內容掛載到同一目標元素上。順序將是簡單的追加 -較晚掛載的將位於目標元素中較早掛載的後面。

給定以下用法

template
<Teleport to="#modals">
  <div>A</div>
</Teleport>
<Teleport to="#modals">
  <div>B</div>
</Teleport>

渲染結果將是

html
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>

延遲 Teleport

在 Vue 3.5 及以上版本中,我們可以使用 defer 屬性將 Teleport 的目標解析延遲到應用程式的其他部分已掛載。這允許 Teleport 針對一個由 Vue 渲染但位於元件樹較晚部分的容器元素。

template
<Teleport defer to="#late-div">...</Teleport>

<!-- somewhere later in the template -->
<div id="late-div"></div>

請注意,目標元素必須在與 Teleport 同一掛載/更新週期中渲染 - 即如果 <div> 只在兩秒後掛載,Teleport 仍然會報告錯誤。defer 與 mounted 生命週期鉤子的工作方式相似。


相關

Teleport 已載入