diff --git a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts index 6d3f6a9b8b6..f6d17cc2a90 100644 --- a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts +++ b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts @@ -4,6 +4,7 @@ import { type ComponentPublicInstance, KeepAlive, type Ref, + Teleport, type TestElement, cloneVNode, createApp, @@ -22,6 +23,7 @@ import { reactive, ref, render, + resolveDynamicComponent, serializeInner, shallowRef, } from '@vue/runtime-test' @@ -977,4 +979,59 @@ describe('KeepAlive', () => { expect(mountedB).toHaveBeenCalledTimes(1) expect(unmountedB).toHaveBeenCalledTimes(0) }) + + // #11410 + test('teleport in keepalive should be cached', async () => { + const activeComponent = shallowRef() + const App = { + name: 'App', + setup() { + const headerRef = ref() + const provided = reactive({ headerRef }) + provide('App', provided) + return () => { + const render = [h('div', { ref: headerRef })] + if (activeComponent.value) { + render.push( + // @ts-expect-error + h(KeepAlive, null, [ + resolveDynamicComponent(h(activeComponent.value)), + ]), + ) + } + return render + } + }, + } + const Comp1 = { + name: 'Comp1', + setup() { + const App = inject('App') as any + return () => h(Teleport, { to: App.headerRef }, ['xxx']) + }, + } + const Comp2 = { + name: 'Comp2', + setup() { + return () => h('div', null, 'Comp2') + }, + } + const root = nodeOps.createElement('div') + render(h(App), root) + activeComponent.value = Comp1 + await nextTick() + expect(serializeInner(root)).toMatchInlineSnapshot( + `"
xxx
"`, + ) + activeComponent.value = Comp2 + await nextTick() + expect(serializeInner(root)).toMatchInlineSnapshot( + `"
Comp2
"`, + ) + activeComponent.value = Comp1 + await nextTick() + expect(serializeInner(root)).toMatchInlineSnapshot( + `"
xxx
"`, + ) + }) }) diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index a2c8c2bf1a7..7dd42fbf16a 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -47,6 +47,7 @@ import { devtoolsComponentAdded } from '../devtools' import { isAsyncWrapper } from '../apiAsyncComponent' import { isSuspense } from './Suspense' import { LifecycleHooks } from '../enums' +import type { TeleportImpl } from './Teleport' type MatchPattern = string | RegExp | (string | RegExp)[] @@ -136,6 +137,14 @@ const KeepAliveImpl: ComponentOptions = { ) => { const instance = vnode.component! move(vnode, container, anchor, MoveType.ENTER, parentSuspense) + processPotentialTeleport(vnode, teleportVnode => { + ;(teleportVnode.type as typeof TeleportImpl).activate( + teleportVnode, + teleportVnode.target!, + null, + sharedContext.renderer, + ) + }) // in case props have changed patch( instance.vnode, @@ -169,8 +178,16 @@ const KeepAliveImpl: ComponentOptions = { const instance = vnode.component! invalidateMount(instance.m) invalidateMount(instance.a) - move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense) + + processPotentialTeleport(vnode, teleportVnode => { + ;(teleportVnode.type as typeof TeleportImpl).deactivate( + teleportVnode, + storageContainer, + null, + sharedContext.renderer, + ) + }) queuePostRenderEffect(() => { if (instance.da) { invokeArrayFns(instance.da) @@ -456,3 +473,32 @@ function resetShapeFlag(vnode: VNode) { function getInnerChild(vnode: VNode) { return vnode.shapeFlag & ShapeFlags.SUSPENSE ? vnode.ssContent! : vnode } + +function processPotentialTeleport( + vnode: VNode, + handle: (teleportVnode: VNode) => void, +) { + if (vnode.shapeFlag & ShapeFlags.TELEPORT) { + handle && handle(vnode) + } + if (vnode.component) { + const subTree = vnode.component.subTree + if (subTree.shapeFlag & ShapeFlags.TELEPORT) + processPotentialTeleport(subTree, handle) + else if (isArray(subTree.children)) { + for (let i = 0; i < subTree.children.length; i++) { + const child = subTree.children[i] + if (child) { + processPotentialTeleport(child as VNode, handle) + } + } + } + } else if (isArray(vnode.children)) { + for (let i = 0; i < vnode.children.length; i++) { + const child = vnode.children[i] + if (child) { + processPotentialTeleport(child as VNode, handle) + } + } + } +} diff --git a/packages/runtime-core/src/components/Teleport.ts b/packages/runtime-core/src/components/Teleport.ts index 65437300cff..47737e6c143 100644 --- a/packages/runtime-core/src/components/Teleport.ts +++ b/packages/runtime-core/src/components/Teleport.ts @@ -292,7 +292,34 @@ export const TeleportImpl = { } } }, - + activate: ( + vnode: VNode, + container: RendererElement, + parentAnchor: RendererNode | null, + internals: RendererInternals, + ) => { + moveTeleport( + vnode, + container, + parentAnchor, + internals, + TeleportMoveTypes.TOGGLE, + ) + }, + deactivate: ( + vnode: VNode, + container: RendererElement, + parentAnchor: RendererNode | null, + internals: RendererInternals, + ) => { + moveTeleport( + vnode, + container, + parentAnchor, + internals, + TeleportMoveTypes.TOGGLE, + ) + }, move: moveTeleport, hydrate: hydrateTeleport, }