最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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)外層的位置去,本文就來(lái)介紹一下如何使用,感興趣的可以了解一下

背景

當(dāng)我們想在 vue 中開(kāi)發(fā)一個(gè)能夠指定位置渲染的組件例如 tooltip、modal 時(shí)可能首先想到的是去引 ui 庫(kù)中的組件,或者自己手寫一個(gè),但 vue3 中提供的內(nèi)置組件 Teleport 能夠幫我們輕松解決問(wèn)題,下面就來(lái)介紹下它的用法以及實(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)單的,然后來(lái)看下需要注意的地方

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)系,也就是說(shuō) 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)元素上劃過(guò)時(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)了,讓我們來(lái)探究下核心源碼是怎么實(shí)現(xiàn)的

原理

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

  • 渲染函數(shù)中保持整潔
  • 當(dāng)我們沒(méi)有使用 Teleport 時(shí),因?yàn)閷⑦@個(gè)渲染邏輯單獨(dú)抽出來(lái),所以可以利用 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
}

下面我們來(lái)看具體是怎么實(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
  }
}

看起來(lái)有些復(fù)雜,不過(guò)讓我們理一下思路:上面我們提到了要實(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)下。好了,有了思路后我們來(lái)嘗試寫出來(lái)

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

const Teleport = {
? __isTeleport: true,
? process(n1, n2, container, anchor, internals) {
? ? //通過(guò)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è)分支來(lái)處理

//如果新舊 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)文章

  • vue中封裝echarts公共組件過(guò)程

    vue中封裝echarts公共組件過(guò)程

    這篇文章主要介紹了vue中封裝echarts公共組件過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-05-05
  • 使用vue-router切換組件時(shí)使組件不銷毀問(wèn)題

    使用vue-router切換組件時(shí)使組件不銷毀問(wèn)題

    這篇文章主要介紹了使用vue-router切換組件時(shí)使組件不銷毀問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • element-ui中select組件綁定值改變,觸發(fā)change事件方法

    element-ui中select組件綁定值改變,觸發(fā)change事件方法

    今天小編就為大家分享一篇element-ui中select組件綁定值改變,觸發(fā)change事件方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-08-08
  • 使用Vite搭建vue3+TS項(xiàng)目的實(shí)現(xiàn)步驟

    使用Vite搭建vue3+TS項(xiàng)目的實(shí)現(xiàn)步驟

    本文主要介紹了使用Vite搭建vue3+TS項(xiàng)目的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-01-01
  • vue組件學(xué)習(xí)教程

    vue組件學(xué)習(xí)教程

    這篇文章主要為大家詳細(xì)介紹了vue組件學(xué)習(xí)教程,根據(jù)Vue官方文檔學(xué)習(xí)的筆記,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-09-09
  • Vue extend學(xué)習(xí)示例講解

    Vue extend學(xué)習(xí)示例講解

    這篇文章主要介紹了Vue.extend使用示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-09-09
  • vue使用axios上傳文件(FormData)的方法

    vue使用axios上傳文件(FormData)的方法

    這篇文章主要介紹了vue使用axios上傳文件(FormData)的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • vuex管理狀態(tài)倉(cāng)庫(kù)使用詳解

    vuex管理狀態(tài)倉(cāng)庫(kù)使用詳解

    這篇文章主要介紹了vuex管理狀態(tài)倉(cāng)庫(kù)使用詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-07-07
  • VueJS 集成 Medium Editor的示例代碼 (自定義編輯器按鈕)

    VueJS 集成 Medium Editor的示例代碼 (自定義編輯器按鈕)

    本篇文章主要介紹了VueJS 集成 Medium Editor的示例代碼 (自定義編輯器按鈕),具有一定的參考價(jià)值,有興趣的可以了解一下
    2017-08-08

最新評(píng)論

安多县| 公主岭市| 澳门| 文成县| 临沧市| 通化县| 苏州市| 绥芬河市| 温宿县| 黔江区| 保康县| 金秀| 汉寿县| 颍上县| 洞头县| 镇赉县| 潜山县| 应用必备| 五寨县| 台南市| 信阳市| 贵德县| 冀州市| 仙居县| 桦南县| 合阳县| 米泉市| 南丰县| 卢氏县| 保德县| 通城县| 茶陵县| 高安市| 西林县| 紫阳县| 黎平县| 牡丹江市| 攀枝花市| 广平县| 彝良县| 文成县|