Skip to content

Teleport

<Teleport> là một thành phần tích hợp cho phép chúng ta "teleport" một phần của mẫu của một thành phần vào một nút DOM tồn tại bên ngoài cây DOM của thành phần đó.

Sử Dụng Cơ Bản

Đôi khi chúng ta có thể gặp tình huống sau: một phần của mẫu của một thành phần thuộc về nó logic, nhưng từ góc nhìn hình ảnh, nó nên được hiển thị ở một nơi khác trong DOM, ngoài ứng dụng Vue.

Ví dụ phổ biến nhất của điều này là khi xây dựng một modal toàn màn hình. Lý tưởng, chúng ta muốn nút modal và modal chính nó sống trong cùng một thành phần, vì cả hai liên quan đến trạng thái mở / đóng của modal. Nhưng điều này có nghĩa là modal sẽ được hiển thị cùng với nút, nằm sâu trong cây DOM của ứng dụng. Điều này có thể tạo ra một số vấn đề phức tạp khi định vị modal thông qua CSS.

Xem xét cấu trúc HTML sau.

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

Và đây là triển khai của <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>

Thành phần chứa một <button> để kích hoạt mở modal và một <div> với một lớp .modal, chứa nội dung của modal và một nút để tự đóng.

Khi sử dụng thành phần này trong cấu trúc HTML ban đầu, có một số vấn đề tiềm ẩn:

  • position: fixed chỉ đặt phần tử liên quan đến viewport khi không có phần tử tổ tiên nào có thuộc tính transform, perspective hoặc filter được thiết lập. Ví dụ, nếu chúng ta dự định tạo hiệu ứng chuyển động cho <div class="outer">, nó sẽ làm hỏng bố cục modal!

  • z-index của modal bị giới hạn bởi các phần tử chứa nó. Nếu có một phần tử khác chồng lên với <div class="outer"> và có z-index cao hơn, nó sẽ che phủ modal của chúng ta.

<Teleport> cung cấp một cách làm sạch để xử lý những vấn đề này, cho phép chúng ta thoát khỏi cấu trúc DOM lồng ghép. Hãy sửa <MyModal> để sử dụng <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>

Mục tiêu to của <Teleport> mong đợi một chuỗi chọn CSS hoặc một nút DOM thực tế. Ở đây, chúng ta đang essentially bảo Vue "teleport fragment này đến thẻ body".

Bạn có thể nhấp vào nút dưới đây và kiểm tra thẻ <body> thông qua devtools của trình duyệt:

Bạn có thể kết hợp <Teleport> với <Transition> để tạo modal có hiệu ứng chuyển động - xem

Ví dụ ở đây.

TIP

Mục tiêu teleport to phải đã tồn tại trong DOM khi thành phần <Teleport> được lắp đặt. Lý tưởng nhất là đây nên là một phần tử bên ngoài toàn bộ ứng dụng Vue. Nếu nhắm vào một phần tử khác được hiển thị bởi Vue, bạn cần đảm bảo phần tử đó được lắp đặt trước <Teleport>.

Sử Dụng với Các Thành Phần

<Teleport> chỉ thay đổi cấu trúc DOM được render - nó không ảnh hưởng đến cấu trúc logic của các thành phần. Điều đó có nghĩa là nếu <Teleport> chứa một thành phần, thành phần đó vẫn sẽ là con logic của thành phần cha chứa <Teleport>. Việc truyền props và phát sự kiện vẫn hoạt động theo cách làm thông thường.

Điều này cũng có nghĩa là các injections từ một thành phần cha vẫn hoạt động như mong đợi và rằng thành phần con sẽ được lồng bên dưới thành phần cha trong Vue Devtools, thay vì được đặt nơi nội dung thực sự đã di chuyển đến.

Tắt Teleport

Trong một số trường hợp, chúng ta có thể muốn tắt <Teleport> theo điều kiện. Ví dụ, chúng ta có thể muốn render một thành phần như một lớp phủ cho máy tính để bàn, nhưng nó được hiển thị dưới dạng nội dung chèn trên di động. <Teleport> hỗ trợ prop disabled có thể được chuyển đổi động:

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

Trong đó, trạng thái isMobile có thể được cập nhật động bởi việc phát hiện thay đổi truy vấn phương tiện.

Nhiều Teleports trên Cùng Một Mục Tiêu

Một trường hợp sử dụng phổ biến là một thành phần <Modal> có thể tái sử dụng, có khả năng có nhiều phiên bản hoạt động cùng một lúc. Đối với loại kịch bản này, nhiều thành phần <Teleport> có thể gắn nội dung của chúng vào cùng một phần tử mục tiêu. Thứ tự sẽ được đơn giản là gắn thêm - những lần gắn sau sẽ được đặt sau những lần gắn trước trong phần tử mục tiêu.

Cho việc sử dụng sau:

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

Kết quả được render sẽ là:

html
<div id="modals">
  <div>A</div>
  <div>B</div>
</div>
Teleport has loaded