詳解Vue3 Teleport 的實踐及原理
Vue3 的組合式 API 以及基于 Proxy 響應(yīng)式原理已經(jīng)有很多文章介紹過了,除了這些比較亮眼的更新,Vue3 還新增了一個內(nèi)置組件: Teleport 。這個組件的作用主要用來將模板內(nèi)的 DOM 元素移動到其他位置。
使用場景
業(yè)務(wù)開發(fā)的過程中,我們經(jīng)常會封裝一些常用的組件,例如 Modal 組件。相信大家在使用 Modal 組件的過程中,經(jīng)常會遇到一個問題,那就是 Modal 的定位問題。
話不多說,我們先寫一個簡單的 Modal 組件。
<!-- Modal.vue -->
<style lang="scss">
.modal {
&__mask {
position: fixed;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background: rgba(0, 0, 0, 0.5);
}
&__main {
margin: 0 auto;
margin-bottom: 5%;
margin-top: 20%;
width: 500px;
background: #fff;
border-radius: 8px;
}
/* 省略部分樣式 */
}
</style>
<template>
<div class="modal__mask">
<div class="modal__main">
<div class="modal__header">
<h3 class="modal__title">彈窗標(biāo)題</h3>
<span class="modal__close">x</span>
</div>
<div class="modal__content">
彈窗文本內(nèi)容
</div>
<div class="modal__footer">
<button>取消</button>
<button>確認(rèn)</button>
</div>
</div>
</div>
</template>
<script>
export default {
setup() {
return {};
},
};
</script>
然后我們在頁面中引入 Modal 組件。
<!-- App.vue -->
<style lang="scss">
.container {
height: 80vh;
margin: 50px;
overflow: hidden;
}
</style>
<template>
<div class="container">
<Modal />
</div>
</template>
<script>
export default {
components: {
Modal,
},
setup() {
return {};
}
};
</script>

如上圖所示, div.container 下彈窗組件正常展示。使用 fixed 進行布局的元素,在一般情況下會相對于屏幕視窗來進行定位,但是如果父元素的 transform , perspective 或 filter 屬性不為 none 時, fixed 元素就會相對于父元素來進行定位。
我們只需要把 .container 類的 transform 稍作修改,彈窗組件的定位就會錯亂。
<style lang="scss">
.container {
height: 80vh;
margin: 50px;
overflow: hidden;
transform: translateZ(0);
}
</style>

這個時候,使用 Teleport 組件就能解決這個問題了。
Teleport 提供了一種干凈的方法,允許我們控制在 DOM 中哪個父節(jié)點下呈現(xiàn) HTML,而不必求助于全局狀態(tài)或?qū)⑵洳鸱譃閮蓚€組件。 -- Vue 官方文檔
我們只需要將彈窗內(nèi)容放入 Teleport 內(nèi),并設(shè)置 to 屬性為 body ,表示彈窗組件每次渲染都會做為 body 的子級,這樣之前的問題就能得到解決。
<template>
<teleport to="body">
<div class="modal__mask">
<div class="modal__main">
...
</div>
</div>
</teleport>
</template>
可以在 https://codesandbox.io/embed/vue-modal-h5g8y 查看代碼。

源碼解析
我們可以先寫一個簡單的模板,然后看看 Teleport 組件經(jīng)過模板編譯后,生成的代碼。
Vue.createApp({
template: `
<Teleport to="body">
<div> teleport to body </div>
</Teleport>
`
})

簡化后代碼:
function render(_ctx, _cache) {
with (_ctx) {
const { createVNode, openBlock, createBlock, Teleport } = Vue
return (openBlock(), createBlock(Teleport, { to: "body" }, [
createVNode("div", null, " teleport to body ", -1 /* HOISTED */)
]))
}
}
可以看到 Teleport 組件通過 createBlock 進行創(chuàng)建。
// packages/runtime-core/src/renderer.ts
export function createBlock(
type, props, children, patchFlag
) {
const vnode = createVNode(
type,
props,
children,
patchFlag
)
// ... 省略部分邏輯
return vnode
}
export function createVNode(
type, props, children, patchFlag
) {
// class & style normalization.
if (props) {
// ...
}
// encode the vnode type information into a bitmap
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT
: __FEATURE_SUSPENSE__ && isSuspense(type)
? ShapeFlags.SUSPENSE
: isTeleport(type)
? ShapeFlags.TELEPORT
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT
: isFunction(type)
? ShapeFlags.FUNCTIONAL_COMPONENT
: 0
const vnode: VNode = {
type,
props,
shapeFlag,
patchFlag,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
}
return vnode
}
// packages/runtime-core/src/components/Teleport.ts
export const isTeleport = type => type.__isTeleport
export const Teleport = {
__isTeleport: true,
process() {}
}
傳入 createBlock 的第一個參數(shù)為 Teleport ,最后得到的 vnode 中會有一個 shapeFlag 屬性,該屬性用來表示 vnode 的類型。 isTeleport(type) 得到的結(jié)果為 true ,所以 shapeFlag 屬性最后的值為 ShapeFlags.TELEPORT ( 1 << 6 )。
// packages/shared/src/shapeFlags.ts
export const enum ShapeFlags {
ELEMENT = 1,
FUNCTIONAL_COMPONENT = 1 << 1,
STATEFUL_COMPONENT = 1 << 2,
TEXT_CHILDREN = 1 << 3,
ARRAY_CHILDREN = 1 << 4,
SLOTS_CHILDREN = 1 << 5,
TELEPORT = 1 << 6,
SUSPENSE = 1 << 7,
COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
COMPONENT_KEPT_ALIVE = 1 << 9
}
在組件的 render 節(jié)點,會依據(jù) type 和 shapeFlag 走不同的邏輯。
// packages/runtime-core/src/renderer.ts
const render = (vnode, container) => {
if (vnode == null) {
// 當(dāng)前組件為空,則將組件銷毀
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
// 新建或者更新組件
// container._vnode 是之前已創(chuàng)建組件的緩存
patch(container._vnode || null, vnode, container)
}
container._vnode = vnode
}
// patch 是表示補丁,用于 vnode 的創(chuàng)建、更新、銷毀
const patch = (n1, n2, container) => {
// 如果新舊節(jié)點的類型不一致,則將舊節(jié)點銷毀
if (n1 && !isSameVNodeType(n1, n2)) {
unmount(n1)
}
const { type, ref, shapeFlag } = n2
switch (type) {
case Text:
// 處理文本
break
case Comment:
// 處理注釋
break
// case ...
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
// 處理 DOM 元素
} else if (shapeFlag & ShapeFlags.COMPONENT) {
// 處理自定義組件
} else if (shapeFlag & ShapeFlags.TELEPORT) {
// 處理 Teleport 組件
// 調(diào)用 Teleport.process 方法
type.process(n1, n2, container...);
} // else if ...
}
}
可以看到,在處理 Teleport 時,最后會調(diào)用 Teleport.process 方法,Vue3 中很多地方都是通過 process 的方式來處理 vnode 相關(guān)邏輯的,下面我們重點看看 Teleport.process 方法做了些什么。
// packages/runtime-core/src/components/Teleport.ts
const isTeleportDisabled = props => props.disabled
export const Teleport = {
__isTeleport: true,
process(n1, n2, container) {
const disabled = isTeleportDisabled(n2.props)
const { shapeFlag, children } = n2
if (n1 == null) {
const target = (n2.target = querySelector(n2.prop.to))
const mount = (container) => {
// compiler and vnode children normalization.
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(children, container)
}
}
if (disabled) {
// 開關(guān)關(guān)閉,掛載到原來的位置
mount(container)
} else if (target) {
// 將子節(jié)點,掛載到屬性 `to` 對應(yīng)的節(jié)點上
mount(target)
}
}
else {
// n1不存在,更新節(jié)點即可
}
}
}
其實原理很簡單,就是將 Teleport 的 children 掛載到屬性 to 對應(yīng)的 DOM 元素中。為了方便理解,這里只是展示了源碼的九牛一毛,省略了很多其他的操作。
總結(jié)
希望在閱讀文章的過程中,大家能夠掌握 Teleport 組件的用法,并使用到業(yè)務(wù)場景中。盡管原理十分簡單,但是我們有了 Teleport 組件,就能輕松解決彈窗元素定位不準(zhǔn)確的問題。
到此這篇關(guān)于詳解Vue3 Teleport 的實踐及原理的文章就介紹到這了,更多相關(guān)Vue3 Teleport組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Vue3中內(nèi)置組件Teleport的基本使用與典型案例
- Vue3?源碼解讀之?Teleport?組件使用示例
- vue3新增Teleport的問題
- vue2如何實現(xiàn)vue3的teleport
- vue3 teleport的使用案例詳解
- Vue3內(nèi)置組件Teleport使用方法詳解
- 詳解Vue3中Teleport的使用
- vue3 Teleport瞬間移動函數(shù)使用方法詳解
- Vue3 Suspense處理異步組件加載的工作原理
- vue3之Suspense加載異步數(shù)據(jù)的使用
- Vue3異步組件Suspense的使用方法詳解
- Vue3 異步組件 suspense使用詳解
- Vue3異步數(shù)據(jù)加載組件suspense的使用方法
- vue3中Teleport和Suspense的具體使用
相關(guān)文章
解決vue項目運行出現(xiàn)warnings?potentially?fixable?with?the?`--fix
這篇文章主要介紹了解決vue項目運行出現(xiàn)warnings?potentially?fixable?with?the?`--fix`?option的報錯問題,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2021-11-11
Vue處理循環(huán)數(shù)據(jù)流程示例精講
這篇文章主要介紹了Vue處理循環(huán)數(shù)據(jù)流程,這個又是一個編程語言,?模版語法里面必不可少的一個,?也是使用業(yè)務(wù)場景使用最多的一個環(huán)節(jié)。所以學(xué)會使用循環(huán)也是重中之重了2023-04-04
vue獲取v-for異步數(shù)據(jù)dom的解決問題
這篇文章主要介紹了vue獲取v-for異步數(shù)據(jù)dom的解決問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03
Vue中通過vue-router實現(xiàn)命名視圖的問題
這篇文章主要介紹了在Vue中通過vue-router實現(xiàn)命名視圖,本文給大家提到了vue-router的原理解析,給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04
vue-quill-editor 自定義工具欄和自定義圖片上傳路徑操作
這篇文章主要介紹了vue-quill-editor 自定義工具欄和自定義圖片上傳路徑操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08
vue使用less報錯:Inline JavaScript is not ena
這篇文章主要介紹了vue使用less報錯:Inline JavaScript is not enabled問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01

