詳解Vue3 Teleport 的實(shí)踐及原理
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
進(jìn)行布局的元素,在一般情況下會相對于屏幕視窗來進(jìn)行定位,但是如果父元素的 transform
, perspective
或 filter
屬性不為 none
時, fixed
元素就會相對于父元素來進(jìn)行定位。
我們只需要把 .container
類的 transform
稍作修改,彈窗組件的定位就會錯亂。
<style lang="scss"> .container { height: 80vh; margin: 50px; overflow: hidden; transform: translateZ(0); } </style>
這個時候,使用 Teleport
組件就能解決這個問題了。
Teleport 提供了一種干凈的方法,允許我們控制在 DOM 中哪個父節(jié)點(diǎn)下呈現(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
進(jìn)行創(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é)點(diǎn),會依據(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 是表示補(bǔ)丁,用于 vnode 的創(chuàng)建、更新、銷毀 const patch = (n1, n2, container) => { // 如果新舊節(jié)點(diǎn)的類型不一致,則將舊節(jié)點(diǎn)銷毀 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)邏輯的,下面我們重點(diǎ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é)點(diǎn),掛載到屬性 `to` 對應(yīng)的節(jié)點(diǎn)上 mount(target) } } else { // n1不存在,更新節(jié)點(diǎn)即可 } } }
其實(shí)原理很簡單,就是將 Teleport
的 children
掛載到屬性 to
對應(yīng)的 DOM 元素中。為了方便理解,這里只是展示了源碼的九牛一毛,省略了很多其他的操作。
總結(jié)
希望在閱讀文章的過程中,大家能夠掌握 Teleport
組件的用法,并使用到業(yè)務(wù)場景中。盡管原理十分簡單,但是我們有了 Teleport
組件,就能輕松解決彈窗元素定位不準(zhǔn)確的問題。
到此這篇關(guān)于詳解Vue3 Teleport 的實(shí)踐及原理的文章就介紹到這了,更多相關(guān)Vue3 Teleport組件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- Vue3中內(nèi)置組件Teleport的基本使用與典型案例
- Vue3?源碼解讀之?Teleport?組件使用示例
- vue3新增Teleport的問題
- vue2如何實(shí)現(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àng)目運(yùn)行出現(xiàn)warnings?potentially?fixable?with?the?`--fix
這篇文章主要介紹了解決vue項(xiàng)目運(yùn)行出現(xiàn)warnings?potentially?fixable?with?the?`--fix`?option的報(bào)錯問題,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2021-11-11Vue處理循環(huán)數(shù)據(jù)流程示例精講
這篇文章主要介紹了Vue處理循環(huán)數(shù)據(jù)流程,這個又是一個編程語言,?模版語法里面必不可少的一個,?也是使用業(yè)務(wù)場景使用最多的一個環(huán)節(jié)。所以學(xué)會使用循環(huán)也是重中之重了2023-04-04vue獲取v-for異步數(shù)據(jù)dom的解決問題
這篇文章主要介紹了vue獲取v-for異步數(shù)據(jù)dom的解決問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-03-03Vue中通過vue-router實(shí)現(xiàn)命名視圖的問題
這篇文章主要介紹了在Vue中通過vue-router實(shí)現(xiàn)命名視圖,本文給大家提到了vue-router的原理解析,給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-04-04vue-quill-editor 自定義工具欄和自定義圖片上傳路徑操作
這篇文章主要介紹了vue-quill-editor 自定義工具欄和自定義圖片上傳路徑操作,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2020-08-08vue使用less報(bào)錯:Inline JavaScript is not ena
這篇文章主要介紹了vue使用less報(bào)錯:Inline JavaScript is not enabled問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01vue3限制table表格選項(xiàng)個數(shù)的解決方法
這篇文章主要為大家詳細(xì)介紹了vue3限制table表格選項(xiàng)個數(shù)的解決方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-04-04