溫馨提示×

溫馨提示×

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

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

Vue3之Teleport組件怎么使用

發(fā)布時間:2022-08-25 17:39:38 來源:億速云 閱讀:201 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容主要講解“Vue3之Teleport組件怎么使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學(xué)習(xí)“Vue3之Teleport組件怎么使用”吧!

Teleport 組件解決的問題

版本:3.2.31

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

Vue.js 3 中內(nèi)建的 Teleport 組件,可以將指定內(nèi)容渲染到特定容器中,而不受DOM層級的限制??梢院芎玫慕鉀Q這個問題。

下面,我們來看看 Teleport 組件是如何解決這個問題的。如下是基于 Teleport 組件實現(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 組件的插槽。

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

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

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

// packages/runtime-core/src/components/Teleport.ts
export const TeleportImpl = {
  // Teleport 組件獨有的特性,用作標(biāo)識
  __isTeleport: true,
  // 客戶端渲染 Teleport 組件
  process() {},
  // 移除 Teleport
  remove() {},
  //  移動 Teleport
  move: moveTeleport,
  // 服務(wù)端渲染 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 組件獨有的特性,用作標(biāo)識。process 函數(shù)是渲染 Teleport 組件的主要渲染邏輯,它從渲染器中分離出來,可以避免渲染器邏輯代碼 “膨脹”。

Teleport 組件 process 函數(shù)

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

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

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

patch 函數(shù)中對 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 的類型為 TELEPORT,則它是 Teleport 組件
        // 調(diào)用 Teleport 組件選項中的 process 函數(shù)將控制權(quán)交接出去
        // 傳遞給 process 函數(shù)的第五個參數(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
          )
        }
        // 省略部分代碼
    }
    // 省略部分代碼
  }

從上面的源碼中可以看到,我們通過vnode 的 shapeFlag 來判斷組件是否是 Teleport 組件。如果是,則直接調(diào)用組件選項中定義的 process 函數(shù)將渲染控制權(quán)完全交接出去,這樣就實現(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)
  // 獲取容器,即掛載點
  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) {
      // 調(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,說明禁用了 <teleport> 的功能,Teleport 只會在 container 中渲染
    mount(container, mainAnchor)
  } else if (target) {
    // 如果沒有禁用 <teleport> 的功能,并且存在掛載點,則將其插槽內(nèi)容渲染到target容中
    mount(target, targetAnchor)
  }
}

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

如果沒有禁用 的功能 (disabled 為 false ),則調(diào)用渲染器內(nèi)部的 mountChildren 方法將 Teleport 組件掛載到目標(biāo)元素中。如果 的功能被禁用,則 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 指定的目標(biāo)掛載點
  const currentContainer = wasDisabled ? container : target
  const currentAnchor = wasDisabled ? mainAnchor : targetAnchor
  // 目標(biāo)掛載點是否是 SVG 標(biāo)簽元素
  isSVG = isSVG || isTargetSVG(target)
  // 動態(tài)子節(jié)點的更新
  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.
    // 確保所有根級節(jié)點在移動之前可以繼承之前的 DOM 引用,以便它們在未來的補丁中移動
    traverseStaticChildren(n1, n2, true)
  } else if (!optimized) {
    // 更新子節(jié)點
    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 是否發(fā)生變化
    // target changed
    // 如果新舊 to 的值不同,則需要對內(nèi)容進(jìn)行移動
    if ((n2.props && n2.props.to) !== (n1.props && n1.props.to)) {
      // 獲取新的目標(biāo)容器
      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 組件的子節(jié)點中有動態(tài)子節(jié)點,則調(diào)用 patchBlockChildren 函數(shù)來更新子節(jié)點,否則就調(diào)用 patchChildren 函數(shù)來更新子節(jié)點。

接下來判斷 Teleport 的功能是否被禁用。如果被禁用了,即 Teleport 組件的 disabled 屬性為 true,此時 Teleport 組件只會在周圍父組件中指定了 的位置渲染。

如果沒有被禁用,那么需要判斷 Teleport 組件的 to 屬性值是否發(fā)生變化。如果發(fā)生變化,則需要獲取新的掛載點,然后調(diào)用 moveTeleport 函數(shù)將Teleport組件掛載到到新的掛載點中。如果沒有發(fā)生變化,則 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.
  // 插入到目標(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é)點
      for (let i = 0; i &lt; (children as VNode[]).length; i++) {
        // 調(diào)用 渲染器的黑布方法 move將子節(jié)點移動到目標(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 組件移動到目標(biāo)掛載點中,實際上就是調(diào)用渲染器的內(nèi)部方法 insert 和 move 來實現(xiàn)子節(jié)點的插入和移動。

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 {
  // 獲取掛載點
  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 組件時,調(diào)用的是服務(wù)端渲染的 hydrateChildren 函數(shù)來渲染Teleport的內(nèi)容。如果 的功能被禁用,將 Teleport 渲染到父組件中指定了 的位置,否則將 Teleport 渲染到目標(biāo)容器target中。

到此,相信大家對“Vue3之Teleport組件怎么使用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!

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

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

AI