溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶(hù)服務(wù)條款》

Vue3之Teleport組件如何使用

發(fā)布時(shí)間:2023-05-17 15:41:31 來(lái)源:億速云 閱讀:111 作者:iii 欄目:編程語(yǔ)言

這篇文章主要介紹了Vue3之Teleport組件如何使用的相關(guān)知識(shí),內(nèi)容詳細(xì)易懂,操作簡(jiǎn)單快捷,具有一定借鑒價(jià)值,相信大家閱讀完這篇Vue3之Teleport組件如何使用文章都會(huì)有所收獲,下面我們一起來(lái)看看吧。

Teleport 組件解決的問(wèn)題

版本:3.2.31

如果要實(shí)現(xiàn)一個(gè) “蒙層” 的功能,并且該 “蒙層” 可以遮擋頁(yè)面上的所有元素,通常情況下我們會(huì)選擇直接在 標(biāo)簽下渲染 “蒙層” 內(nèi)容。如果在Vue.js 2 中實(shí)現(xiàn)這個(gè)功能,只能通過(guò)原生 DOM API 來(lái)手動(dòng)搬運(yùn) DOM元素實(shí)現(xiàn),這就會(huì)使得元素的渲染與 Vue.js 的渲染機(jī)制脫節(jié),并會(huì)導(dǎo)致各種可預(yù)見(jiàn)或不可遇見(jiàn)的問(wèn)題。

Vue.js 3 中內(nèi)建的 Teleport 組件,可以將指定內(nèi)容渲染到特定容器中,而不受DOM層級(jí)的限制。可以很好的解決這個(gè)問(wèn)題。

下面,我們來(lái)看看 Teleport 組件是如何解決這個(gè)問(wèn)題的。如下是基于 Teleport 組件實(shí)現(xiàn)的蒙層組件的模板:

<template>
  <Teleport to="body">
    <div class="overlay"></div>
  </Teleport>
</template>
<style scoped>
  .verlay {
    z-index: 9999;
  }
</style>

可以看到,蒙層組件要渲染的內(nèi)容都包含在 Teleport 組件內(nèi),即作為 Teleport 組件的插槽。

通過(guò)為 Teleport 組件指定渲染目標(biāo) body,即 to 屬性的值,該組件就會(huì)把它的插槽內(nèi)容渲染到 body 下,而不會(huì)按照模板的 DOM 層級(jí)來(lái)渲染,于是就實(shí)現(xiàn)了跨 DOM 層級(jí)的渲染。

從而實(shí)現(xiàn)了蒙層可以遮擋頁(yè)面中的所有內(nèi)容。

Teleport 組件的基本結(jié)構(gòu)

// packages/runtime-core/src/components/Teleport.ts
export const TeleportImpl = {
  // Teleport 組件獨(dú)有的特性,用作標(biāo)識(shí)
  __isTeleport: true,
  // 客戶(hù)端渲染 Teleport 組件
  process() {},
  // 移除 Teleport
  remove() {},
  //  移動(dòng) Teleport
  move: moveTeleport,
  // 服務(wù)端渲染 Teleport
  hydrate: hydrateTeleport
}
export const Teleport = TeleportImpl as any as {
  __isTeleport: true
  new (): { $props: VNodeProps & TeleportProps }
}

我們對(duì) Teleport 組件的源碼做了精簡(jiǎn),如上面的代碼所示,可以看到,一個(gè)組件就是一個(gè)選項(xiàng)對(duì)象。Teleport 組件上有 __isTeleport、process、remove、move、hydrate 等屬性。其中 __isTeleport 屬性是 Teleport 組件獨(dú)有的特性,用作標(biāo)識(shí)。process 函數(shù)是渲染 Teleport 組件的主要渲染邏輯,它從渲染器中分離出來(lái),可以避免渲染器邏輯代碼 “膨脹”。

Teleport 組件 process 函數(shù)

process 函數(shù)主要用于在客戶(hù)端渲染 Teleport 組件。由于 Teleport 組件需要渲染器的底層支持,因此將 Teleport 組件的渲染邏輯從渲染器中分離出來(lái),在 Teleport 組件中實(shí)現(xiàn)其渲染邏輯。這么做有以下兩點(diǎn)好處:

  • 可以避免渲染器邏輯代碼 “膨脹”;

  • 當(dāng)用戶(hù)沒(méi)有使用 Teleport 組件時(shí),由于 Teleport 的渲染邏輯被分離,因此可以利用 Tree-Shaking 機(jī)制在最終的 bundle 中刪除 Teleport 相關(guān)的代碼,使得最終構(gòu)建包的體積變小。

patch 函數(shù)中對(duì) process 函數(shù)的調(diào)用如下:

// 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 的類(lèi)型為 TELEPORT,則它是 Teleport 組件
        // 調(diào)用 Teleport 組件選項(xiàng)中的 process 函數(shù)將控制權(quán)交接出去
        // 傳遞給 process 函數(shù)的第五個(gè)參數(shù)是渲染器的一些內(nèi)部方法
        else if (shapeFlag & ShapeFlags.TELEPORT) {
          ;(type as typeof TeleportImpl).process(
            n1 as TeleportVNode,
            n2 as TeleportVNode,
            container,
            anchor,
            parentComponent,
            parentSuspense,
            isSVG,
            slotScopeIds,
            optimized,
            internals
          )
        }
        // 省略部分代碼
    }
    // 省略部分代碼
  }

從上面的源碼中可以看到,我們通過(guò)vnode 的 shapeFlag 來(lái)判斷組件是否是 Teleport 組件。如果是,則直接調(diào)用組件選項(xiàng)中定義的 process 函數(shù)將渲染控制權(quán)完全交接出去,這樣就實(shí)現(xiàn)了渲染邏輯的分離。

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)
  // 獲取容器,即掛載點(diǎn)
  const target = (n2.target = resolveTarget(n2.props, querySelector))
  const targetAnchor = (n2.targetAnchor = createText(''))
  // 如果掛載點(diǎn)存在,則將
  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 渲染到指定掛載點(diǎn)
  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) {
      // 調(diào)用渲染器內(nèi)部的 mountChildren 方法渲染 Teleport 組件的插槽內(nèi)容
      mountChildren(
        children as VNodeArrayChildren,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized
      )
    }
  }
  // 掛載 Teleport
  if (disabled) {
    // 如果 Teleport 組件的 disabled 為 true,說(shuō)明禁用了 <teleport> 的功能,Teleport 只會(huì)在 container 中渲染
    mount(container, mainAnchor)
  } else if (target) {
    // 如果沒(méi)有禁用 <teleport> 的功能,并且存在掛載點(diǎn),則將其插槽內(nèi)容渲染到target容中
    mount(target, targetAnchor)
  }
}

從上面的源碼中可以看到,如果舊的虛擬節(jié)點(diǎn) (n1) 不存在,則執(zhí)行 Teleport 組件的掛載。然后調(diào)用 resolveTarget 函數(shù),根據(jù) props.to 屬性的值來(lái)取得真正的掛載點(diǎn)。

如果沒(méi)有禁用 的功能 (disabled 為 false ),則調(diào)用渲染器內(nèi)部的 mountChildren 方法將 Teleport 組件掛載到目標(biāo)元素中。如果 的功能被禁用,則 Teleport 組件將會(huì)在周?chē)附M件中指定了 的位置渲染。

Teleport 組件的更新

Teleport 組件在更新時(shí)需要考慮多種情況,如下面的代碼所示:

// packages/runtime-core/src/components/Teleport.ts
else {
  // 更新 Teleport 組件
  // update content
  n2.el = n1.el
  const mainAnchor = (n2.anchor = n1.anchor)!
  // 掛載點(diǎn)
  const target = (n2.target = n1.target)!
  // 錨點(diǎn)
  const targetAnchor = (n2.targetAnchor = n1.targetAnchor)!
  // 判斷 Teleport 組件是否禁用了 
  const wasDisabled = isTeleportDisabled(n1.props)
  // 如果禁用了 <teleport> 的功能,那么掛載點(diǎn)就是周?chē)附M件,否則就是 to 指定的目標(biāo)掛載點(diǎn)
  const currentContainer = wasDisabled ? container : target
  const currentAnchor = wasDisabled ? mainAnchor : targetAnchor
  // 目標(biāo)掛載點(diǎn)是否是 SVG 標(biāo)簽元素
  isSVG = isSVG || isTargetSVG(target)
  // 動(dòng)態(tài)子節(jié)點(diǎn)的更新
  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.
    // 確保所有根級(jí)節(jié)點(diǎn)在移動(dòng)之前可以繼承之前的 DOM 引用,以便它們?cè)谖磥?lái)的補(bǔ)丁中移動(dòng)
    traverseStaticChildren(n1, n2, true)
  } else if (!optimized) {
    // 更新子節(jié)點(diǎn)
    patchChildren(
      n1,
      n2,
      currentContainer,
      currentAnchor,
      parentComponent,
      parentSuspense,
      isSVG,
      slotScopeIds,
      false
    )
  }
  // 如果禁用了 <teleport> 的功能
  if (disabled) {
    if (!wasDisabled) {
      // enabled -> disabled
      // move into main container
      // 將 Teleport 移動(dòng)到container容器中
      moveTeleport(
        n2,
        container,
        mainAnchor,
        internals,
        TeleportMoveTypes.TOGGLE
      )
    }
  } else {
    // 沒(méi)有禁用 <teleport> 的功能,判斷 to 是否發(fā)生變化
    // target changed
    // 如果新舊 to 的值不同,則需要對(duì)內(nèi)容進(jìn)行移動(dòng)
    if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) {
      // 獲取新的目標(biāo)容器
      const nextTarget = (n2.target = resolveTarget(
        n2.props,
        querySelector
      ))
      if (nextTarget) {
        // 移動(dòng)到新的容器中
        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 組件的子節(jié)點(diǎn)中有動(dòng)態(tài)子節(jié)點(diǎn),則調(diào)用 patchBlockChildren 函數(shù)來(lái)更新子節(jié)點(diǎn),否則就調(diào)用 patchChildren 函數(shù)來(lái)更新子節(jié)點(diǎn)。

接下來(lái)判斷 Teleport 的功能是否被禁用。如果被禁用了,即 Teleport 組件的 disabled 屬性為 true,此時(shí) Teleport 組件只會(huì)在周?chē)附M件中指定了 的位置渲染。

如果沒(méi)有被禁用,那么需要判斷 Teleport 組件的 to 屬性值是否發(fā)生變化。如果發(fā)生變化,則需要獲取新的掛載點(diǎn),然后調(diào)用 moveTeleport 函數(shù)將Teleport組件掛載到到新的掛載點(diǎn)中。如果沒(méi)有發(fā)生變化,則 Teleport 組件將會(huì)掛載到先的掛載點(diǎn)中。

moveTeleport 移動(dòng)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.
  // 插入到目標(biāo)容器中
  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) {
    // 插入到目標(biāo)容器中
    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) {
      // 遍歷子節(jié)點(diǎn)
      for (let i = 0; i &lt; (children as VNode[]).length; i++) {
        // 調(diào)用 渲染器的黑布方法 move將子節(jié)點(diǎn)移動(dòng)到目標(biāo)元素中
        move(
          (children as VNode[])[i],
          container,
          parentAnchor,
          MoveType.REORDER
        )
      }
    }
  }
  // move main view anchor if this is a re-order.
  if (isReorder) {
    // 插入到目標(biāo)容器中
    insert(anchor!, container, parentAnchor)
  }
}

從上面的源碼中可以看到,將 Teleport 組件移動(dòng)到目標(biāo)掛載點(diǎn)中,實(shí)際上就是調(diào)用渲染器的內(nèi)部方法 insert 和 move 來(lái)實(shí)現(xiàn)子節(jié)點(diǎn)的插入和移動(dòng)。

hydrateTeleport 服務(wù)端渲染 Teleport 組件

hydrateTeleport 函數(shù)用于在服務(wù)器端渲染 Teleport 組件,其源碼如下:

// packages/runtime-core/src/components/Teleport.ts
// 服務(wù)端渲染 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 {
  // 獲取掛載點(diǎn)
  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 渲染到目標(biāo)容器中
        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)
}

可以看到,在服務(wù)端渲染 Teleport 組件時(shí),調(diào)用的是服務(wù)端渲染的 hydrateChildren 函數(shù)來(lái)渲染Teleport的內(nèi)容。如果 的功能被禁用,將 Teleport 渲染到父組件中指定了 的位置,否則將 Teleport 渲染到目標(biāo)容器target中。

關(guān)于“Vue3之Teleport組件如何使用”這篇文章的內(nèi)容就介紹到這里,感謝各位的閱讀!相信大家對(duì)“Vue3之Teleport組件如何使用”知識(shí)都有一定的了解,大家如果還想學(xué)習(xí)更多知識(shí),歡迎關(guān)注億速云行業(yè)資訊頻道。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI