目录
1. Teleport 传送组件
1.1 为什么要使用 Teleport
1.2 如何使用 Teleport
1.3 Teleport 源码
2. keep-alive 缓存组件
2.1 为什么要使用 keep-alive
2.2 开启 keep-alive 时的生命周期
2.3 keep-alive 使用方法
2.4 keep-alive 源码
Teleport 是 Vue3 新特性,可以把组件渲染到指定位置,类似于 React 的 Portal
假设定义了一个弹框组件,他的外容器有 position:absolute 样式,那么这个样式极其容易被父元素影响;
也就是说:如果弹窗在不同的父元素里,可能会被干扰定位,展现不同的效果;
使用 Teleport 可以把弹框组件挪动到 body 上,避免挂载到其他父节点上,同时还能使用特定页面的数据;
综上所述:在 A页面 中引入弹窗组件,弹窗组件使用 A页面 的数据,使用 Teleport 后,弹框组件 最终节点不挂载到 A页面 的容器上,而是挂载到 body 上
to 指的是 teleport 内包裹的内容,会被挂载到哪个元素(支持 id、class)的 同级位置
disabled 如果设为 true,则 to 属性失效
源码位置:core-main\packages\runtime-core\src\renderer.ts
通过 patch 函数,判断是不是 teleport 组件,如果是,则执行 process 方法
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
slotScopeIds = null,
optimized = __DEV__ && isHmrUpdating ? false : !!n2.dynamicChildren
) => {
if (n1 === n2) {
return
}
...
const { type, ref, shapeFlag } = n2
switch (type) {
case Text:
...
case Comment:
...
case Static:
...
case Fragment:
...
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
...
} else if (shapeFlag & ShapeFlags.COMPONENT) {
...
// 如果是 teleport 组件,则会调用 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
)
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
...
}
// set ref
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2)
}
}
下面是 创建、更新、删除逻辑:
export const TeleportImpl = {
__isTeleport: true,
// 创建、更新
process(
n1: TeleportVNode | null,
n2: TeleportVNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
slotScopeIds: string[] | null,
optimized: boolean,
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
// #3302
// HMR updated, force full diff
if (__DEV__ && isHmrUpdating) {
optimized = false
dynamicChildren = null
}
if (n1 == null) {
// 在主视图插入空白节点或空白文本
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)
// 关键代码,获取目标元素的 DOM 节点(在这里调用方法 resolveTarget 获取 to 属性值,并通过 querySelector 取到那个元素)
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})`)
}
// 向目标元素挂载节点
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,
parentSuspense,
isSVG,
slotScopeIds,
optimized
)
}
}
if (disabled) {
// 若 disabled 为 true,则挂载到原来的位置,不变化
mount(container, mainAnchor)
} else if (target) {
// 若 disabled 为 false,则挂载到新的目标节点(to 属性指定的位置)
mount(target, targetAnchor)
}
} else {
// update content
// 更新逻辑
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
isSVG = isSVG || isTargetSVG(target)
// 更新子节点处理 disabled 变化的逻辑
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.
traverseStaticChildren(n1, n2, true)
} else if (!optimized) {
patchChildren(
n1,
n2,
currentContainer,
currentAnchor,
parentComponent,
parentSuspense,
isSVG,
slotScopeIds,
false
)
}
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 (__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
)
}
}
}
},
// 移除
remove(
vnode: VNode,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
optimized: boolean,
{ um: unmount, o: { remove: hostRemove } }: RendererInternals,
doRemove: Boolean
) {
const { shapeFlag, children, anchor, targetAnchor, target, props } = vnode
if (target) {
hostRemove(targetAnchor!)
}
// an unmounted teleport should always remove its children if not disabled
if (doRemove || !isTeleportDisabled(props)) {
hostRemove(anchor!)
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
for (let i = 0; i < (children as VNode[]).length; i++) {
const child = (children as VNode[])[i]
unmount(
child,
parentComponent,
parentSuspense,
true,
!!child.dynamicChildren
)
}
}
}
},
move: moveTeleport,
hydrate: hydrateTeleport
}
参考文章:是个萌妹大佬,快关注他
学习Vue3 第十九章(Teleport传送组件)_小满zs的博客-CSDN博客Teleport Vue 3.0新特性之一。Teleport 是一种能够将我们的模板渲染至指定DOM节点,不受父级style、v-show等属性影响,但data、prop数据依旧能够共用的技术;类似于 React 的 Portal。主要解决的问题 因为Teleport节点挂载在其他指定的DOM节点下,完全不受父级style样式影响使用方法通过to 属性 插入指定元素位置 to="body" 便可以将Teleport 内容传送到指定位置
不希望组件被重新渲染,影响使用体验;希望提升性能
举个栗子:两个表单切换时,默认情况下,会清空填写内容;若使用了 keep-alive,则会保留原来填写的内容
keep-alive 存在时,组件只会执行一次 onMounted 函数;不会执行 onUnMounted 函数;会反复执行 onActivated、onDeactivated 函数;具体过程如下:
注意事项:
二者可以接收三种类型的参数:用逗号分隔的字符串、正则表达式或一个数组
举个栗子:
// 只缓存 A B 组件
// 缓存 除了 C 组件外的组件
最多缓存的组件数,会使用一种算法,缓存最活跃的组件们
// 最多缓存10个组件
...
源码位置:runtime-core/src/components/KeepAlive.ts
强烈推荐直接听小满的讲解视频:
小满Vue3(第二十章 keep-alive缓存组件 & 源码解析)_哔哩哔哩_bilibili小满Vue3(第二十章 keep-alive缓存组件 & 源码解析)是Vue3 + vite + Ts + pinia + 实战 + 源码 +electron的第22集视频,该合集共计110集,视频收藏或关注UP主,及时了解更多相关视频内容。https://www.bilibili.com/video/BV1dS4y1y7vd?p=22&spm_id_from=pageDriver&vd_source=8bc01635b95dbe8ecd349b2c23b03a10