亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

Vue內(nèi)置組件Teleport的使用

 更新時(shí)間:2023年05月22日 09:41:52   作者:Junior_FE_2022  
Teleport是一個(gè)內(nèi)置組件,它可以將一個(gè)組件內(nèi)部的一部分模板“傳送”到該組件的?DOM?結(jié)構(gòu)外層的位置去,本文就來介紹一下如何使用,感興趣的可以了解一下

背景

當(dāng)我們想在 vue 中開發(fā)一個(gè)能夠指定位置渲染的組件例如 tooltip、modal 時(shí)可能首先想到的是去引 ui 庫(kù)中的組件,或者自己手寫一個(gè),但 vue3 中提供的內(nèi)置組件 Teleport 能夠幫我們輕松解決問題,下面就來介紹下它的用法以及實(shí)現(xiàn)原理

正文

官網(wǎng)對(duì)于它的介紹是: <Teleport> 是一個(gè)內(nèi)置組件,它可以將一個(gè)組件內(nèi)部的一部分模板“傳送”到該組件的 DOM 結(jié)構(gòu)外層的位置去

用法及屬性

<Teleport to="body" :disabled="disabled">
? <div></div>
</Teleport>
//to: 傳送的目標(biāo),dom對(duì)象/css選擇器字符串 //disabled: 是否禁用
const disabled = ref<boolean>(false)

用法還是挺簡(jiǎn)單的,然后來看下需要注意的地方

Tip

  • <Teleport> 掛載時(shí),傳送的 to 目標(biāo)必須已經(jīng)存在于 DOM 中。如果目標(biāo)元素也是由 Vue 渲染的,需要確保在掛載 <Teleport> 之前先掛載該元素
  • 可以搭配組件使用,只改變了渲染的 DOM 結(jié)構(gòu),它不會(huì)影響組件間的邏輯關(guān)系,<Teleport> 和內(nèi)部組件始終保持父子關(guān)系,也就是說 props 和 provide 都可以正常使用
  • 多個(gè) Teleport 會(huì)共享目標(biāo),多個(gè) <Teleport> 組件可以將其內(nèi)容掛載在同一個(gè)目標(biāo)元素上,而順序就是順次追加
<Teleport to=".modal">
  <div>A</div>
</Teleport>
<Teleport to=".modal">
  <div>B</div>
</Teleport>

結(jié)果:

<div class=".modal">
  <div>A</div>
  <div>B</div>
</div>

實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 tooltip

<div
  v-for="(item, index) in array"
  :key="index"
  @mousemove="handleMousemove($event, index)"
  @mouseleave="handleMouseleave"
></div>
<teleport to="body">
  <div
    class="max-w-60vw rounded-10px p-10px border-2px fixed border-indigo-300 bg-white"
    ref="toolTipRef"
    :style="{
            left: tooltipStyle.x,
            top: tooltipStyle.y,
            opacity: tooltipStyle.opacity,
        }"
  >
    <span>{ tooltipStyle.content }</span>
  </div>
</teleport>

實(shí)現(xiàn)的大概邏輯就是鼠標(biāo)目標(biāo)元素上劃過時(shí)更改 teleport 內(nèi)部元素的透明度,移除時(shí)將透明度改為 0

<script lang="ts" setup>
? ? const tooltipStyle = reactive({
? ? ? ? x: '0px',
? ? ? ? y: '0px',
? ? ? ? content: '',
? ? ? ? opacity: 0,
? ? });
? ? const array = ref<string[]>(['test'])
? ? const handleMousemove = (e: MouseEvent, index: number) => {
? ? ? ? tooltipStyle.opacity = 1;
? ? ? ? tooltipStyle.x = e.x + 10 + 'px';
? ? ? ? tooltipStyle.y = e.y + 10 + 'px';
? ? ? ? tooltipStyle.content = array.value[index]
? ? };
? ? const handleMouseleave = () => {
? ? ? ? tooltipStyle.opacity = 0;
? ? };
</script>

看完用法,接著就到本文的重點(diǎn)了,讓我們來探究下核心源碼是怎么實(shí)現(xiàn)的

原理

首先我們可以考慮的問題是將 teleport 的渲染和正常 vnode 的渲染分離開,這樣做的優(yōu)點(diǎn)是:

  • 渲染函數(shù)中保持整潔
  • 當(dāng)我們沒有使用 Teleport 時(shí),因?yàn)閷⑦@個(gè)渲染邏輯單獨(dú)抽出來,所以可以利用 tree-shaking 將相關(guān)的代碼刪除。所以 vue 的做法是針對(duì) teleport 組件重新寫了套渲染代碼:

在 renderer 的 patch 函數(shù)中,如果遇到類型是 teleport 的,就使用自己的掛載方法,這里的 TeleportImpl 就是具體的實(shí)現(xiàn)對(duì)象

else if (shapeFlag & ShapeFlags.TELEPORT) {
    ;(type as typeof TeleportImpl).process(
        n1 as TeleportVNode,
        n2 as TeleportVNode,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        isSVG,
        slotScopeIds,
        optimized,
        internals
    )
}

移動(dòng)函數(shù)move

if (shapeFlag & ShapeFlags.TELEPORT) {
    ;(type as typeof TeleportImpl).move(vnode, container, anchor, internals)
    return
}

下面我們來看具體是怎么實(shí)現(xiàn)的(保留核心代碼)

對(duì)應(yīng)的位置在倉(cāng)庫(kù)的Teleport.ts文件中

const TeleportImpl = {
? process(
? ? n1: TeleportVNode | null,
? ? n2: TeleportVNode,
? ? container: RendererElement,
? ? anchor: RendererNode | null,
? ? parentComponent: ComponentInternalInstance | null,
? ? internals: RendererInternals
? ) {
? ? const {
? ? ? mc: mountChildren,
? ? ? pc: patchChildren,
? ? ? pbc: patchBlockChildren,
? ? ? o: { insert, querySelector, createText, createComment },
? ? } = internals
? ? const disabled = isTeleportDisabled(n2.props)
? ? let { shapeFlag, children, dynamicChildren } = n2
? ? //如果是首次掛載
? ? if (n1 == null) {
? ? ? // insert anchors in the main view
? ? ? const placeholder = (n2.el = createText(''))
? ? ? const mainAnchor = (n2.anchor = createText(''))
? ? ? insert(placeholder, container, anchor)
? ? ? insert(mainAnchor, container, anchor)
? ? ? //resolveTarget處理傳入的to屬性
? ? ? const target = (n2.target = resolveTarget(n2.props, querySelector))
? ? ? const targetAnchor = (n2.targetAnchor = createText(''))
? ? ? if (target) {
? ? ? ? insert(targetAnchor, target)
? ? ? }
? ? ? //自己的掛載方法
? ? ? 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(children as VNodeArrayChildren, container, anchor, parentComponent)
? ? ? ? }
? ? ? }
? ? ? if (disabled) {
? ? ? ? mount(container, mainAnchor)
? ? ? } else if (target) {
? ? ? ? mount(target, targetAnchor)
? ? ? }
? ? } else {
? ? ? // 更新
? ? ? n2.el = n1.el
? ? ? const mainAnchor = (n2.anchor = n1.anchor)!
? ? ? const target = (n2.target = n1.target)!
? ? ? const targetAnchor = (n2.targetAnchor = n1.targetAnchor)!
? ? ? const wasDisabled = isTeleportDisabled(n1.props)
? ? ? const currentContainer = wasDisabled ? container : target
? ? ? const currentAnchor = wasDisabled ? mainAnchor : targetAnchor
? ? ? if (dynamicChildren) {
? ? ? ? // fast path when the teleport happens to be a block root
? ? ? ? patchBlockChildren(n1.dynamicChildren!, dynamicChildren, currentContainer, parentComponent)
? ? ? ? // 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.
? ? ? ? traverseStaticChildren(n1, n2, true)
? ? ? }
? ? ? if (disabled) {
? ? ? ? if (!wasDisabled) {
? ? ? ? ? // enabled -> disabled
? ? ? ? ? // move into main container
? ? ? ? ? moveTeleport(n2, container, mainAnchor, internals, TeleportMoveTypes.TOGGLE)
? ? ? ? }
? ? ? } else {
? ? ? ? // target changed
? ? ? ? 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 (wasDisabled) {
? ? ? ? ? // disabled -> enabled
? ? ? ? ? // move into teleport target
? ? ? ? ? moveTeleport(n2, target, targetAnchor, internals, TeleportMoveTypes.TOGGLE)
? ? ? ? }
? ? ? }
? ? }
? },
}

然后我們看 process 里的重要函數(shù)

moveTarget

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((children as VNode[])[i], container, parentAnchor, MoveType.REORDER)
      }
    }
  }
  // move main view anchor if this is a re-order.
  if (isReorder) {
    insert(anchor!, container, parentAnchor)
  }
}

resolveTarget

const resolveTarget = <T = RendererElement>(
  props: TeleportProps | null,
  select: RendererOptions['querySelector']
): T | null => {
  const targetSelector = props && props.to
  if (isString(targetSelector)) {
    if (!select) {
      return null
    } else {
      const target = select(targetSelector)
      return target as any
    }
  } else {
    return targetSelector as any
  }
}

看起來有些復(fù)雜,不過讓我們理一下思路:上面我們提到了要實(shí)現(xiàn)自己的渲染方法,所以我們可以先寫基本的渲染函數(shù),然后在內(nèi)部需要區(qū)分是首次掛載還是組件更新,但如果是 teleport 的接收的參數(shù)更改了呢,所以這時(shí)候就要去主動(dòng)實(shí)現(xiàn)一個(gè) move 函數(shù)將內(nèi)容移動(dòng)到新的節(jié)點(diǎn)下。好了,有了思路后我們來嘗試寫出來

先來實(shí)現(xiàn)組件的掛載與更新

const Teleport = {
? __isTeleport: true,
? process(n1, n2, container, anchor, internals) {
? ? //通過internals拿到渲染器內(nèi)部方法
? ? const { patch, patchChildren } = internals
? ? // 如果oldVnode不存在,就是全新掛載
? ? if (!n1) {
? ? ? //mount
? ? ? //獲取掛載點(diǎn)
? ? ? const target =
? ? ? ? typeof n2.props.to === 'string' ? document.querySelector(n2.props.to) : n2.props.to
? ? ? //將newVnode掛載
? ? ? n2.children.forEach((child) => patch(null, child, target, anchor))
? ? } else {
? ? ? //更新
? ? ? patchChildren(n1, n2, container)
? ? }
? },
}

上面我們提到了更新,但如果是 to 屬性更改了呢,所以需要有個(gè)分支來處理

//如果新舊 to 參數(shù)不同,需要對(duì)內(nèi)容移動(dòng)
if (n2.props.to !== n1.props.to) {
  //獲取新容器
  const newTarget =
    typeof n2.props.to === 'string' ? document.querySelector(n2.props.to) : n2.props.to
  //移動(dòng)到新的容器上
  n2.children.forEach((child) => move(child, newTarget))
}

傳入 move 函數(shù)

else if (shapeFlag & ShapeFlags.TELEPORT) {
    type.process(n1, n2, container, anchor, internals) {
        patch,
        patchChildren,
        move(vnode, container, anchor) {
            //這里只處理了組件或者普通元素
            const el = vnode.component ? vnode.component.subTree.el : vnode.el
            const { insert } = internals
            insert(el, container, anchor)
        }
    }
}

這里我們省略了處理 disabled 的 remove 函數(shù),不是本文研究的重點(diǎn),具體可以看源碼的 Teleport 文件

到此這篇關(guān)于Vue內(nèi)置組件Teleport的使用的文章就介紹到這了,更多相關(guān)Vue Teleport內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評(píng)論