diff --git a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts index 1e4176b6fb8..3309ca0b8b1 100644 --- a/packages/runtime-core/__tests__/components/KeepAlive.spec.ts +++ b/packages/runtime-core/__tests__/components/KeepAlive.spec.ts @@ -1173,4 +1173,53 @@ describe('KeepAlive', () => { expect(deactivatedHome).toHaveBeenCalledTimes(0) expect(unmountedHome).toHaveBeenCalledTimes(1) }) + + test('should work with async component when update `include` props', async () => { + let resolve: (comp: Component) => void + const AsyncComp = defineAsyncComponent( + () => + new Promise(r => { + resolve = r as any + }), + ) + + const toggle = ref(true) + const instanceRef = ref(null) + const keepaliveInclude = ref(['Foo']) + const App = { + render: () => { + return h(KeepAlive, { include: keepaliveInclude.value }, () => + toggle.value ? h(AsyncComp, { ref: instanceRef }) : null, + ) + }, + } + + render(h(App), root) + // async component has not been resolved + expect(serializeInner(root)).toBe('') + + resolve!({ + name: 'Foo', + data: () => ({ count: 0 }), + render() { + return h('p', this.count) + }, + }) + + await timeout() + // resolved + expect(serializeInner(root)).toBe('

0

') + + // change state + toggle out + update `include` props + instanceRef.value.count++ + toggle.value = false + keepaliveInclude.value = ['Foo'] + await nextTick() + expect(serializeInner(root)).toBe('') + + // toggle in, state should be maintained + toggle.value = true + await nextTick() + expect(serializeInner(root)).toBe('

1

') + }) }) diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index f2b7bdf9738..55eaf862443 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -202,7 +202,13 @@ const KeepAliveImpl: ComponentOptions = { function pruneCache(filter: (name: string) => boolean) { cache.forEach((vnode, key) => { - const name = getComponentName(vnode.type as ConcreteComponent) + // for async components, name check should be based in its loaded + // inner component if available + const name = getComponentName( + isAsyncWrapper(vnode) + ? (vnode.type as ComponentOptions).__asyncResolved || {} + : (vnode.type as ConcreteComponent), + ) if (name && !filter(name)) { pruneCacheEntry(key) }