本篇內容主要講解“Vue3之Teleport組件怎么使用”,感興趣的朋友不妨來看看。本文介紹的方法操作簡單快捷,實用性強。下面就讓小編來帶大家學習“Vue3之Teleport組件怎么使用”吧!
版本:3.2.31
如果要實現一個 “蒙層” 的功能,并且該 “蒙層” 可以遮擋頁面上的所有元素,通常情況下我們會選擇直接在 標簽下渲染 “蒙層” 內容。如果在Vue.js 2 中實現這個功能,只能通過原生 DOM API 來手動搬運 DOM元素實現,這就會使得元素的渲染與 Vue.js 的渲染機制脫節,并會導致各種可預見或不可遇見的問題。
Vue.js 3 中內建的 Teleport 組件,可以將指定內容渲染到特定容器中,而不受DOM層級的限制??梢院芎玫慕鉀Q這個問題。
下面,我們來看看 Teleport 組件是如何解決這個問題的。如下是基于 Teleport 組件實現的蒙層組件的模板:
<template>
<Teleport to="body">
<div class="overlay"></div>
</Teleport>
</template>
<style scoped>
.verlay {
z-index: 9999;
}
</style>可以看到,蒙層組件要渲染的內容都包含在 Teleport 組件內,即作為 Teleport 組件的插槽。
通過為 Teleport 組件指定渲染目標 body,即 to 屬性的值,該組件就會把它的插槽內容渲染到 body 下,而不會按照模板的 DOM 層級來渲染,于是就實現了跨 DOM 層級的渲染。
從而實現了蒙層可以遮擋頁面中的所有內容。
// packages/runtime-core/src/components/Teleport.ts
export const TeleportImpl = {
// Teleport 組件獨有的特性,用作標識
__isTeleport: true,
// 客戶端渲染 Teleport 組件
process() {},
// 移除 Teleport
remove() {},
// 移動 Teleport
move: moveTeleport,
// 服務端渲染 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 組件獨有的特性,用作標識。process 函數是渲染 Teleport 組件的主要渲染邏輯,它從渲染器中分離出來,可以避免渲染器邏輯代碼 “膨脹”。
process 函數主要用于在客戶端渲染 Teleport 組件。由于 Teleport 組件需要渲染器的底層支持,因此將 Teleport 組件的渲染邏輯從渲染器中分離出來,在 Teleport 組件中實現其渲染邏輯。這么做有以下兩點好處:
可以避免渲染器邏輯代碼 “膨脹”;
當用戶沒有使用 Teleport 組件時,由于 Teleport 的渲染邏輯被分離,因此可以利用 Tree-Shaking 機制在最終的 bundle 中刪除 Teleport 相關的代碼,使得最終構建包的體積變小。
patch 函數中對 process 函數的調用如下:
// 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 組件
// 調用 Teleport 組件選項中的 process 函數將控制權交接出去
// 傳遞給 process 函數的第五個參數是渲染器的一些內部方法
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 組件。如果是,則直接調用組件選項中定義的 process 函數將渲染控制權完全交接出去,這樣就實現了渲染邏輯的分離。
// 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) {
// 調用渲染器內部的 mountChildren 方法渲染 Teleport 組件的插槽內容
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> 的功能,并且存在掛載點,則將其插槽內容渲染到target容中
mount(target, targetAnchor)
}
}從上面的源碼中可以看到,如果舊的虛擬節點 (n1) 不存在,則執行 Teleport 組件的掛載。然后調用 resolveTarget 函數,根據 props.to 屬性的值來取得真正的掛載點。
如果沒有禁用 的功能 (disabled 為 false ),則調用渲染器內部的 mountChildren 方法將 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 指定的目標掛載點
const currentContainer = wasDisabled ? container : target
const currentAnchor = wasDisabled ? mainAnchor : targetAnchor
// 目標掛載點是否是 SVG 標簽元素
isSVG = isSVG || isTargetSVG(target)
// 動態子節點的更新
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.
// 確保所有根級節點在移動之前可以繼承之前的 DOM 引用,以便它們在未來的補丁中移動
traverseStaticChildren(n1, n2, true)
} else if (!optimized) {
// 更新子節點
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 是否發生變化
// target changed
// 如果新舊 to 的值不同,則需要對內容進行移動
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 (__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 組件的子節點中有動態子節點,則調用 patchBlockChildren 函數來更新子節點,否則就調用 patchChildren 函數來更新子節點。
接下來判斷 Teleport 的功能是否被禁用。如果被禁用了,即 Teleport 組件的 disabled 屬性為 true,此時 Teleport 組件只會在周圍父組件中指定了 的位置渲染。
如果沒有被禁用,那么需要判斷 Teleport 組件的 to 屬性值是否發生變化。如果發生變化,則需要獲取新的掛載點,然后調用 moveTeleport 函數將Teleport組件掛載到到新的掛載點中。如果沒有發生變化,則 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.
// 插入到目標容器中
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將子節點移動到目標元素中
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)
}
}從上面的源碼中可以看到,將 Teleport 組件移動到目標掛載點中,實際上就是調用渲染器的內部方法 insert 和 move 來實現子節點的插入和移動。
hydrateTeleport 函數用于在服務器端渲染 Teleport 組件,其源碼如下:
// packages/runtime-core/src/components/Teleport.ts
// 服務端渲染 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 渲染到目標容器中
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)
}可以看到,在服務端渲染 Teleport 組件時,調用的是服務端渲染的 hydrateChildren 函數來渲染Teleport的內容。如果 的功能被禁用,將 Teleport 渲染到父組件中指定了 的位置,否則將 Teleport 渲染到目標容器target中。
到此,相信大家對“Vue3之Teleport組件怎么使用”有了更深的了解,不妨來實際操作一番吧!這里是億速云網站,更多相關內容可以進入相關頻道進行查詢,關注我們,繼續學習!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。