您好,登錄后才能下訂單哦!
這篇文章將為大家詳細講解有關Vue3中Teleport 組件的原理是什么,文章內容質量較高,因此小編分享給大家做個參考,希望大家閱讀完這篇文章后對相關知識有一定的了解。
使用場景
業務開發的過程中,我們經常會封裝一些常用的組件,例如 Modal 組件。相信大家在使用 Modal 組件的過程中,經常會遇到一個問題,那就是 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"> <h4 class="modal__title">彈窗標題</h4> <span class="modal__close">x</span> </div> <div class="modal__content"> 彈窗文本內容 </div> <div class="modal__footer"> <button>取消</button> <button>確認</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>
Modal
如上圖所示, div.container 下彈窗組件正常展示。使用 fixed 進行布局的元素,在一般情況下會相對于屏幕視窗來進行定位,但是如果父元素的 transform, perspective 或 filter 屬性不為 none 時,fixed 元素就會相對于父元素來進行定位。
我們只需要把 .container 類的 transform 稍作修改,彈窗組件的定位就會錯亂。
<style lang="scss"> .container { height: 80vh; margin: 50px; overflow: hidden; transform: translateZ(0); } </style>
Modal
這個時候,使用 Teleport 組件就能解決這個問題了。
“Teleport 提供了一種干凈的方法,允許我們控制在 DOM 中哪個父節點下呈現 HTML,而不必求助于全局狀態或將其拆分為兩個組件。-- Vue 官方文檔
我們只需要將彈窗內容放入 Teleport 內,并設置 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-h6g8y 查看代碼。
使用 Teleport 的 Modal
源碼解析
我們可以先寫一個簡單的模板,然后看看 Teleport 組件經過模板編譯后,生成的代碼。
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 進行創建。
// 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 的第一個參數為 Teleport,最后得到的 vnode 中會有一個 shapeFlag 屬性,該屬性用來表示 vnode 的類型。isTeleport(type) 得到的結果為 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 節點,會依據 type 和 shapeFlag 走不同的邏輯。
// packages/runtime-core/src/renderer.ts const render = (vnode, container) => { if (vnode == null) { // 當前組件為空,則將組件銷毀 if (container._vnode) { unmount(container._vnode, null, null, true) } } else { // 新建或者更新組件 // container._vnode 是之前已創建組件的緩存 patch(container._vnode || null, vnode, container) } container._vnode = vnode } // patch 是表示補丁,用于 vnode 的創建、更新、銷毀 const patch = (n1, n2, container) => { // 如果新舊節點的類型不一致,則將舊節點銷毀 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 組件 // 調用 Teleport.process 方法 type.process(n1, n2, container...); } // else if ... } }
可以看到,在處理 Teleport 時,最后會調用 Teleport.process 方法,Vue3 中很多地方都是通過 process 的方式來處理 vnode 相關邏輯的,下面我們重點看看 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) { // 開關關閉,掛載到原來的位置 mount(container) } else if (target) { // 將子節點,掛載到屬性 `to` 對應的節點上 mount(target) } } else { // n1不存在,更新節點即可 } } }
其實原理很簡單,就是將 Teleport 的 children 掛載到屬性 to 對應的 DOM 元素中。為了方便理解,這里只是展示了源碼的九牛一毛,省略了很多其他的操作。
關于Vue3中Teleport 組件的原理是什么就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。