欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue3?源碼解讀之?Teleport?組件使用示例

 更新時(shí)間:2022年08月19日 11:33:59   作者:紫圣  
這篇文章主要為大家介紹了Vue3?源碼解讀之?Teleport?組件使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

Teleport 組件解決的問題

版本:3.2.31

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

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

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

通過為 Teleport 組件指定渲染目標(biāo) body,即 to 屬性的值,該組件就會(huì)把它的插槽內(nèi)容渲染到 body 下,而不會(huì)按照模板的 DOM 層級(jí)來渲染,于是就實(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,
  // 客戶端渲染 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 組件的主要渲染邏輯,它從渲染器中分離出來,可以避免渲染器邏輯代碼 “膨脹”。

Teleport 組件 process 函數(shù)

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

  • 可以避免渲染器邏輯代碼 “膨脹”;
  • 當(dāng)用戶沒有使用 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 的類型為 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
          )
        }
        // 省略部分代碼
    }
    // 省略部分代碼
  }

從上面的源碼中可以看到,我們通過vnode 的 shapeFlag 來判斷組件是否是 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,說明禁用了 <teleport> 的功能,Teleport 只會(huì)在 container 中渲染
    mount(container, mainAnchor)
  } else if (target) {
    // 如果沒有禁用 <teleport> 的功能,并且存在掛載點(diǎn),則將其插槽內(nèi)容渲染到target容中
    mount(target, targetAnchor)
  }
}

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

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

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)就是周圍父組件,否則就是 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è)谖磥淼难a(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 {
    // 沒有禁用 <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ù)來更新子節(jié)點(diǎn),否則就調(diào)用 patchChildren 函數(shù)來更新子節(jié)點(diǎn)。

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

如果沒有被禁用,那么需要判斷 Teleport 組件的 to 屬性值是否發(fā)生變化。如果發(fā)生變化,則需要獲取新的掛載點(diǎn),然后調(diào)用 moveTeleport 函數(shù)將Teleport組件掛載到到新的掛載點(diǎn)中。如果沒有發(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 來實(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ù)來渲染Teleport的內(nèi)容。如果 的功能被禁用,將 Teleport 渲染到父組件中指定了 的位置,否則將 Teleport 渲染到目標(biāo)容器target中。

總結(jié)

本文介紹了 Teleport 組件索要解決的問題和它的實(shí)現(xiàn)原理。Teleport 組件可以跨越 DOM 層級(jí)完成渲染。在實(shí)現(xiàn) Teleport 組件時(shí),將 Teleport 組件的渲染邏輯 (即 Teleport 組件的 process 函數(shù)) 從渲染器中分離出來,是為了避免渲染器邏輯代碼 “膨脹” 以及可以利用 Tree-Shaking 機(jī)制在最終的 bundle 中刪除 Teleport 相關(guān)的代碼,使得最終構(gòu)建包的體積變小。

Teleport 組件在掛載時(shí)會(huì)根據(jù) 的功能是否禁用從而將其掛載到相應(yīng)的掛載點(diǎn)中。在更新時(shí)同樣會(huì)根據(jù) 的功能是否被禁用以及 to 屬性值是否發(fā)生變化,從而將其移動(dòng)到相應(yīng)的掛載點(diǎn)中。

以上就是Vue3 源碼解讀之 Teleport 組件使用示例的詳細(xì)內(nèi)容,更多關(guān)于Vue3 Teleport組件的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • vue操作下拉選擇器獲取選擇的數(shù)據(jù)的id方法

    vue操作下拉選擇器獲取選擇的數(shù)據(jù)的id方法

    今天小編就為大家分享一篇vue操作下拉選擇器獲取選擇的數(shù)據(jù)的id方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧
    2018-08-08
  • vue操作dom并為dom增加點(diǎn)擊事件方式

    vue操作dom并為dom增加點(diǎn)擊事件方式

    這篇文章主要介紹了vue操作dom并為dom增加點(diǎn)擊事件方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-04-04
  • Vue表單類的父子組件數(shù)據(jù)傳遞示例

    Vue表單類的父子組件數(shù)據(jù)傳遞示例

    本篇文章主要介紹了Vue表單類的父子組件數(shù)據(jù)傳遞示例,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2018-05-05
  • 淺談Vue組件及組件的注冊(cè)方法

    淺談Vue組件及組件的注冊(cè)方法

    本文主要介紹了淺談Vue組件及組件的注冊(cè)方法,詳細(xì)的介紹了什么是組件,及其組件注冊(cè)的兩種方式(全局和局部),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-08-08
  • keep-alive不能緩存多層級(jí)路由菜單問題解決

    keep-alive不能緩存多層級(jí)路由菜單問題解決

    這篇文章主要介紹了keep-alive不能緩存多層級(jí)路由菜單問題解決,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-03-03
  • vuex狀態(tài)管理淺談之mapState用法

    vuex狀態(tài)管理淺談之mapState用法

    當(dāng)一個(gè)組件需要獲取多個(gè)狀態(tài)的時(shí)候,將這些狀態(tài)都聲明為計(jì)算屬性會(huì)有些重復(fù)和冗余,為了解決這個(gè)問題我們可以使用mapState輔助函數(shù)幫助我們生成計(jì)算屬性,這篇文章主要給大家介紹了關(guān)于vuex狀態(tài)管理之mapState用法的相關(guān)資料,需要的朋友可以參考下
    2023-12-12
  • 一起來學(xué)習(xí)Vue的生命周期

    一起來學(xué)習(xí)Vue的生命周期

    這篇文章主要為大家詳細(xì)介紹了Vue的生命周期,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-02-02
  • vue中使用el-dropdown方式

    vue中使用el-dropdown方式

    這篇文章主要介紹了vue中使用el-dropdown方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • vue3緩存頁(yè)面keep-alive及路由統(tǒng)一處理詳解

    vue3緩存頁(yè)面keep-alive及路由統(tǒng)一處理詳解

    當(dāng)我們不想每次跳轉(zhuǎn)路由都會(huì)重新加載頁(yè)面時(shí)(重新加載頁(yè)面很耗時(shí)),就可以考慮使用keep-alive緩存頁(yè)面了,這篇文章主要給大家介紹了關(guān)于vue3緩存頁(yè)面keep-alive及路由統(tǒng)一處理的相關(guān)資料,需要的朋友可以參考下
    2021-10-10
  • vue webpack打包后圖片路徑錯(cuò)誤的完美解決方法

    vue webpack打包后圖片路徑錯(cuò)誤的完美解決方法

    這篇文章主要介紹了vue webpack打包后圖片路徑錯(cuò)誤的解決方法,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-12-12

最新評(píng)論