Teleport 是什么?它解決的是什么問題?
一、使用場景 - 為什么我們需要 Teleport
Teleport 是一種能夠將我們的模板移動到 DOM 中 Vue app 之外的其他位置的技術。
1、使用場景:
業務開發的過程中,我們經常會封裝一些常用的組件,例如 Modal 組件。相信大家在使用 Modal 組件的過程中,經常會遇到一個問題,那就是 Modal 的定位問題。
(1)像 modals,toast 等這樣的元素,很多情況下,我們將它完全的和我們的 Vue 應用的 DOM 完全剝離,管理起來反而會方便容易很多。原因在於如果我們嵌套在 Vue 的某個組件內部,那么處理嵌套組件的定位、z-index 和樣式就會變得很困難。
(2)另外,像 modals、toast 等這樣的元素需要使用到 Vue 組件的狀態(data 或者 props)的值。
2、作用 - 這就是 Teleport 派上用場的地方:
我們可以在組件的邏輯位置寫模板代碼,這意味着我們可以使用組件的 data 或 props,然后在 Vue 應用的范圍之外渲染它。
Teleport 提供了一種干凈的方法,允許我們控制在 DOM 中哪個父節點下呈現 HTML,而不必求助於全局狀態或將其拆分為兩個組件。 -- Vue 官方文檔
<teleport> 組件可以將其內容添加到同一目標元素。
<teleport to="#modals">
<div>A</div>
</teleport>
<teleport to="#modals">
<div>B</div>
</teleport>
// result
<div id="modals">
<div>A</div>
<div>B</div>
</div>
二、React 的 Portals 特性
介紹了 Teleport 之后我們也來了解一下 React 的 Portals 特性。Portal 提供了一種將子節點渲染到存在於父組件以外的 DOM 節點的優秀的方案。
ReactDOM.createPortal(child, container)
第一個參數(child)是任何可渲染的 React 子元素,例如一個元素,字符串或 fragment。第二個參數(container)是一個 DOM 元素。
render() { // React 掛載了一個新的 div,並且把子元素渲染其中
return ( <div> {this.props.children} </div> ); }
然而,有時候將子元素插入到 DOM 節點中的不同位置也是有好處的:
render() { // React 並沒有創建一個新的 div。它只是把子元素渲染到 `domNode` 中。 // `domNode` 是一個可以在任何位置的有效 DOM 節點。
return ReactDOM.createPortal( this.props.children, domNode ); }
一個 portal 的典型用例是當父組件有 overflow: hidden 或 z-index 樣式時,但你需要子組件能夠在視覺上“跳出”其容器。例如,對話框、懸浮卡以及提示框。常見的情況是創建一個包含全屏模式的組件。
對話框 position: absolute 的定位相對於父 div 作為參考。Teleport 提供了一種簡單的方法,使我們可以控制要在DOM中哪個父對象下呈現HTML。
三、Teleport 使用
1、代碼示例:直接使用
// 1、index.html 中,我們加一個和 app 同級的 div
<div id="app"></div>
<div id="teleport-target"></div>
// 2、HelloWorld.vue 中,添加如下,留意 to 屬性跟上面的 id 選擇器一致
<button @click="showToast" class="btn">打開 toast</button>
// to 屬性就是目標位置
<teleport to="#teleport-target">
<div v-if="visible" class="toast-wrap">
<div class="toast-msg">我是一個 Toast 文案</div>
</div>
</teleport>
// 3、再使用 js 我們使用組件里的 變量 去控制 toast 顯示隱藏
import { ref } from 'vue'; export default { setup() { // toast 的封裝
const visible = ref(false); let timer; const showToast = () => { visible.value = true; clearTimeout(timer); timer = setTimeout(() => { visible.value = false; }, 2000); } return { visible, showToast } } }
可以看到,我們使用 teleport 組件:
(1)通過 to 屬性,指定該組件渲染的位置與 <div id="app"></div> 同級,也就是在 body 下,
teleport 的狀態 visible 又是完全由內部 Vue 組件控制
2、代碼示例:與 Vue 組件一起使用 - modal
如果 <teleport> 包含 Vue 組件,則它仍將是 <teleport> 父組件的邏輯子組件
<teleport to="#modal-container">
// use the modal component, pass in the prop
<modal :show="showModal" @close="showModal = false">
<template #header>
<h3>custom header</h3>
</template>
</modal>
</teleport> import { ref } from 'vue'; import Modal from './Modal.vue'; export default { components: { Modal }, setup() { // modal 的封裝
const showModal = ref(false); return { showModal } } }
在這種情況下,即使在不同的地方渲染 Modal,它仍將是當前組件(調用 Modal 的組件)的子級,並將從中接收 prop,這也意味着來自父組件的注入按預期工作,並且子組件將嵌套在 Vue Devtools 中的父組件之下,而不是放在實際內容移動到的位置。
3、在同一目標上使用多個teleport
一個常見的用例場景是一個可重用的<Modal>組件,它可能同時有多個實例處於活動狀態。
四、Teleport API
1、to - string,需要prop,必須是有效的查詢選擇器或HTMLElement(如果在瀏覽器環境中使用)。指定將在其中一棟<teleport>內容的目標元素
2、disabled - boolean,此可選屬性可用於禁用<teleport>的功能,這意味着其插槽內容將不會移動到任何位置,而是在您在周圍父組件中指定了<teleport>的位置渲染。
<teleport to="#popup" :disabled="displayVideoInline">
<video src="./my-movie.mp4">
</teleport>
請注意,這將移動實際的DOM節點,而不是被銷毀和重新創建,並且它還將保持任何組件實例的活動狀態。所有有狀態的HTML元素(即播放的視頻)都將保持其狀態。
