温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

Vue3之Teleport组件怎么使用

发布时间:2022-08-25 17:39:38 来源:亿速云 阅读:250 作者:iii 栏目:开发技术

本篇内容主要讲解“Vue3之Teleport组件怎么使用”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“Vue3之Teleport组件怎么使用”吧!

Teleport 组件解决的问题

版本: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 层级的渲染。

从而实现了蒙层可以遮挡页面中的所有内容。

Teleport 组件的基本结构

// 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 组件的主要渲染逻辑,它从渲染器中分离出来,可以避免渲染器逻辑代码 “膨胀”。

Teleport 组件 process 函数

process 函数主要用于在客户端渲染 Teleport 组件。由于 Teleport 组件需要渲染器的底层支持,因此将 Teleport 组件的渲染逻辑从渲染器中分离出来,在 Teleport 组件中实现其渲染逻辑。这么做有以下两点好处:

  • 可以避免渲染器逻辑代码 “膨胀”;

  • 当用户没有使用 Teleport 组件时,由于 Teleport 的渲染逻辑被分离,因此可以利用 Tree-Shaking 机制在最终的 bundle 中删除 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 函数将渲染控制权完全交接出去,这样就实现了渲染逻辑的分离。

Teleport 组件的挂载

// 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 组件的更新

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 组件将会挂载到先的挂载点中。

moveTeleport 移动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 &amp; ShapeFlags.ARRAY_CHILDREN) {       // 遍历子节点       for (let i = 0; i &lt; (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 组件

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中。

到此,相信大家对“Vue3之Teleport组件怎么使用”有了更深的了解,不妨来实际操作一番吧!这里是亿速云网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI