<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
版本:3.2.31
如果要實現一個 “蒙層” 的功能,並且該 “蒙層” 可以遮擋頁面上的所有元素,通常情況下我們會選擇直接在 標籤下渲染 “蒙層” 內容。如果在Vue.js 2 中實現這個功能,只能通過原生 DOM API 來手動搬運 DOM元素實現,這就會使得元素的渲染與 Vue.js 的渲染機制脫節,並會導致各種可預見或不可遇見的問題。
Vue.js 3 中內建的 Teleport 元件,可以將指定內容渲染到特定容器中,而不受DOM層級的限制。可以很好的解決這個問題。
下面,我們來看看 Teleport 元件是如何解決這個問題的。如下是基於 Teleport 元件實現的蒙層元件的模板:
<template> <Teleport to="body"> <div class="overlay"></div> </Teleport> </template> <style scoped> .verlay { z-index: 9999; } </style>
可以看到,蒙層元件要渲染的內容都包含在 Teleport 元件內,即作為 Teleport 元件的插槽。
通過為 Teleport 元件指定渲染目標 body,即 to 屬性的值,該元件就會把它的插槽內容渲染到 body 下,而不會按照模板的 DOM 層級來渲染,於是就實現了跨 DOM 層級的渲染。
從而實現了蒙層可以遮擋頁面中的所有內容。
// packages/runtime-core/src/components/Teleport.ts export const TeleportImpl = { // Teleport 元件獨有的特性,用作標識 __isTeleport: true, // 使用者端渲染 Teleport 元件 process() {}, // 移除 Teleport remove() {}, // 移動 Teleport move: moveTeleport, // 伺服器端渲染 Teleport hydrate: hydrateTeleport } export const Teleport = TeleportImpl as any as { __isTeleport: true new (): { $props: VNodeProps & TeleportProps } }
我們對 Teleport 元件的原始碼做了精簡,如上面的程式碼所示,可以看到,一個元件就是一個選項物件。Teleport 元件上有 __isTeleport、process、remove、move、hydrate 等屬性。其中 __isTeleport 屬性是 Teleport 元件獨有的特性,用作標識。process 函數是渲染 Teleport 元件的主要渲染邏輯,它從渲染器中分離出來,可以避免渲染器邏輯程式碼 “膨脹”。
process 函數主要用於在使用者端渲染 Teleport 元件。由於 Teleport 元件需要渲染器的底層支援,因此將 Teleport 元件的渲染邏輯從渲染器中分離出來,在 Teleport 元件中實現其渲染邏輯。這麼做有以下兩點好處:
patch 函數中對 process 函數的呼叫如下:
// packages/runtime-core/src/renderer.ts const patch: PatchFn = ( n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, isSVG = false, slotScopeIds = null, optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren ) => { // 省略部分程式碼 const { type, ref, shapeFlag } = n2 switch (type) { // 省略部分程式碼 default: // 省略部分程式碼 // shapeFlag 的型別為 TELEPORT,則它是 Teleport 元件 // 呼叫 Teleport 元件選項中的 process 函數將控制權交接出去 // 傳遞給 process 函數的第五個引數是渲染器的一些內部方法 else if (shapeFlag & ShapeFlags.TELEPORT) { ;(type as typeof TeleportImpl).process( n1 as TeleportVNode, n2 as TeleportVNode, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized, internals ) } // 省略部分程式碼 } // 省略部分程式碼 }
從上面的原始碼中可以看到,我們通過vnode 的 shapeFlag 來判斷元件是否是 Teleport 元件。如果是,則直接呼叫元件選項中定義的 process 函數將渲染控制權完全交接出去,這樣就實現了渲染邏輯的分離。
// packages/runtime-core/src/components/Teleport.ts if (n1 == null) { // 首次渲染 Teleport // insert anchors in the main view // 往 container 中插入 Teleport 的註釋 const placeholder = (n2.el = __DEV__ ? createComment('teleport start') : createText('')) const mainAnchor = (n2.anchor = __DEV__ ? createComment('teleport end') : createText('')) insert(placeholder, container, anchor) insert(mainAnchor, container, anchor) // 獲取容器,即掛載點 const target = (n2.target = resolveTarget(n2.props, querySelector)) const targetAnchor = (n2.targetAnchor = createText('')) // 如果掛載點存在,則將 if (target) { insert(targetAnchor, target) // #2652 we could be teleporting from a non-SVG tree into an SVG tree isSVG = isSVG || isTargetSVG(target) } else if (__DEV__ && !disabled) { warn('Invalid Teleport target on mount:', target, `(${typeof target})`) } // 將 n2.children 渲染到指定掛載點 const mount = (container: RendererElement, anchor: RendererNode) => { // Teleport *always* has Array children. This is enforced in both the // compiler and vnode children normalization. if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { // 呼叫渲染器內部的 mountChildren 方法渲染 Teleport 元件的插槽內容 mountChildren( children as VNodeArrayChildren, container, anchor, parentComponent, parentSuspense, isSVG, slotScopeIds, optimized ) } } // 掛載 Teleport if (disabled) { // 如果 Teleport 元件的 disabled 為 true,說明禁用了 <teleport> 的功能,Teleport 只會在 container 中渲染 mount(container, mainAnchor) } else if (target) { // 如果沒有禁用 <teleport> 的功能,並且存在掛載點,則將其插槽內容渲染到target容中 mount(target, targetAnchor) } }
從上面的原始碼中可以看到,如果舊的虛擬節點 (n1) 不存在,則執行 Teleport 元件的掛載。然後呼叫 resolveTarget 函數,根據 props.to 屬性的值來取得真正的掛載點。
如果沒有禁用 的功能 (disabled 為 false ),則呼叫渲染器內部的 mountChildren 方法將 Teleport 元件掛載到目標元素中。如果 的功能被禁用,則 Teleport 元件將會在周圍父元件中指定了 的位置渲染。
Teleport 元件在更新時需要考慮多種情況,如下面的程式碼所示:
// packages/runtime-core/src/components/Teleport.ts else { // 更新 Teleport 元件 // update content n2.el = n1.el const mainAnchor = (n2.anchor = n1.anchor)! // 掛載點 const target = (n2.target = n1.target)! // 錨點 const targetAnchor = (n2.targetAnchor = n1.targetAnchor)! // 判斷 Teleport 元件是否禁用了 const wasDisabled = isTeleportDisabled(n1.props) // 如果禁用了 <teleport> 的功能,那麼掛載點就是周圍父元件,否則就是 to 指定的目標掛載點 const currentContainer = wasDisabled ? container : target const currentAnchor = wasDisabled ? mainAnchor : targetAnchor // 目標掛載點是否是 SVG 標籤元素 isSVG = isSVG || isTargetSVG(target) // 動態子節點的更新 if (dynamicChildren) { // fast path when the teleport happens to be a block root patchBlockChildren( n1.dynamicChildren!, dynamicChildren, currentContainer, parentComponent, parentSuspense, isSVG, slotScopeIds ) // even in block tree mode we need to make sure all root-level nodes // in the teleport inherit previous DOM references so that they can // be moved in future patches. // 確保所有根級節點在移動之前可以繼承之前的 DOM 參照,以便它們在未來的修補程式中移動 traverseStaticChildren(n1, n2, true) } else if (!optimized) { // 更新子節點 patchChildren( n1, n2, currentContainer, currentAnchor, parentComponent, parentSuspense, isSVG, slotScopeIds, false ) } // 如果禁用了 <teleport> 的功能 if (disabled) { if (!wasDisabled) { // enabled -> disabled // move into main container // 將 Teleport 移動到container容器中 moveTeleport( n2, container, mainAnchor, internals, TeleportMoveTypes.TOGGLE ) } } else { // 沒有禁用 <teleport> 的功能,判斷 to 是否發生變化 // target changed // 如果新舊 to 的值不同,則需要對內容進行移動 if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) { // 獲取新的目標容器 const nextTarget = (n2.target = resolveTarget( n2.props, querySelector )) if (nextTarget) { // 移動到新的容器中 moveTeleport( n2, nextTarget, null, internals, TeleportMoveTypes.TARGET_CHANGE ) } else if (__DEV__) { warn( 'Invalid Teleport target on update:', target, `(${typeof target})` ) } } else if (wasDisabled) { // disabled -> enabled // move into teleport target // moveTeleport( n2, target, targetAnchor, internals, TeleportMoveTypes.TOGGLE ) } } }
如果 Teleport 元件的子節點中有動態子節點,則呼叫 patchBlockChildren 函數來更新子節點,否則就呼叫 patchChildren 函數來更新子節點。
接下來判斷 Teleport 的功能是否被禁用。如果被禁用了,即 Teleport 元件的 disabled 屬性為 true,此時 Teleport 元件只會在周圍父元件中指定了 的位置渲染。
如果沒有被禁用,那麼需要判斷 Teleport 元件的 to 屬性值是否發生變化。如果發生變化,則需要獲取新的掛載點,然後呼叫 moveTeleport 函數將Teleport元件掛載到到新的掛載點中。如果沒有發生變化,則 Teleport 元件將會掛載到先的掛載點中。
// packages/runtime-core/src/components/Teleport.ts function moveTeleport( vnode: VNode, container: RendererElement, parentAnchor: RendererNode | null, { o: { insert }, m: move }: RendererInternals, moveType: TeleportMoveTypes = TeleportMoveTypes.REORDER ) { // move target anchor if this is a target change. // 插入到目標容器中 if (moveType === TeleportMoveTypes.TARGET_CHANGE) { insert(vnode.targetAnchor!, container, parentAnchor) } const { el, anchor, shapeFlag, children, props } = vnode const isReorder = moveType === TeleportMoveTypes.REORDER // move main view anchor if this is a re-order. if (isReorder) { // 插入到目標容器中 insert(el!, container, parentAnchor) } // if this is a re-order and teleport is enabled (content is in target) // do not move children. So the opposite is: only move children if this // is not a reorder, or the teleport is disabled if (!isReorder || isTeleportDisabled(props)) { // Teleport has either Array children or no children. if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { // 遍歷子節點 for (let i = 0; i < (children as VNode[]).length; i++) { // 呼叫 渲染器的黑布方法 move將子節點移動到目標元素中 move( (children as VNode[])[i], container, parentAnchor, MoveType.REORDER ) } } } // move main view anchor if this is a re-order. if (isReorder) { // 插入到目標容器中 insert(anchor!, container, parentAnchor) } }
從上面的原始碼中可以看到,將 Teleport 元件移動到目標掛載點中,實際上就是呼叫渲染器的內部方法 insert 和 move 來實現子節點的插入和移動。
hydrateTeleport 函數用於在伺服器端渲染 Teleport 元件,其原始碼如下:
// packages/runtime-core/src/components/Teleport.ts // 伺服器端渲染 Teleport function hydrateTeleport( node: Node, vnode: TeleportVNode, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, slotScopeIds: string[] | null, optimized: boolean, { o: { nextSibling, parentNode, querySelector } }: RendererInternals<Node, Element>, hydrateChildren: ( node: Node | null, vnode: VNode, container: Element, parentComponent: ComponentInternalInstance | null, parentSuspense: SuspenseBoundary | null, slotScopeIds: string[] | null, optimized: boolean ) => Node | null ): Node | null { // 獲取掛載點 const target = (vnode.target = resolveTarget<Element>( vnode.props, querySelector )) if (target) { // if multiple teleports rendered to the same target element, we need to // pick up from where the last teleport finished instead of the first node const targetNode = (target as TeleportTargetElement)._lpa || target.firstChild if (vnode.shapeFlag & ShapeFlags.ARRAY_CHILDREN) { // <teleport> 的功能被禁用,將 Teleport 渲染到父元件中指定了 <teleport> 的位置 if (isTeleportDisabled(vnode.props)) { vnode.anchor = hydrateChildren( nextSibling(node), vnode, parentNode(node)!, parentComponent, parentSuspense, slotScopeIds, optimized ) vnode.targetAnchor = targetNode } else { vnode.anchor = nextSibling(node) // 將 Teleport 渲染到目標容器中 vnode.targetAnchor = hydrateChildren( targetNode, vnode, target, parentComponent, parentSuspense, slotScopeIds, optimized ) } ;(target as TeleportTargetElement)._lpa = vnode.targetAnchor && nextSibling(vnode.targetAnchor as Node) } } return vnode.anchor && nextSibling(vnode.anchor as Node) }
可以看到,在伺服器端渲染 Teleport 元件時,呼叫的是伺服器端渲染的 hydrateChildren 函數來渲染Teleport的內容。如果 的功能被禁用,將 Teleport 渲染到父元件中指定了 的位置,否則將 Teleport 渲染到目標容器target中。
本文介紹了 Teleport 元件索要解決的問題和它的實現原理。Teleport 元件可以跨越 DOM 層級完成渲染。在實現 Teleport 元件時,將 Teleport 元件的渲染邏輯 (即 Teleport 元件的 process 函數) 從渲染器中分離出來,是為了避免渲染器邏輯程式碼 “膨脹” 以及可以利用 Tree-Shaking 機制在最終的 bundle 中刪除 Teleport 相關的程式碼,使得最終構建包的體積變小。
Teleport 元件在掛載時會根據 的功能是否禁用從而將其掛載到相應的掛載點中。在更新時同樣會根據 的功能是否被禁用以及 to 屬性值是否發生變化,從而將其移動到相應的掛載點中。
以上就是Vue3 原始碼解讀之 Teleport 元件使用範例的詳細內容,更多關於Vue3 Teleport元件的資料請關注it145.com其它相關文章!
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45