diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts index a448972e139..8de5b3182d0 100644 --- a/packages/runtime-core/__tests__/components/Suspense.spec.ts +++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts @@ -2021,7 +2021,7 @@ describe('Suspense', () => { viewRef.value = 0 await nextTick() - expect(serializeInner(root)).toBe('') + expect(serializeInner(root)).toBe('
sync
') await Promise.all(deps) await nextTick() @@ -2035,6 +2035,56 @@ describe('Suspense', () => { expect(serializeInner(root)).toBe(`
sync
`) }) + // #10899 + test('KeepAlive + Suspense switch before branch resolves', async () => { + const Async1 = defineAsyncComponent({ + render() { + return h('div', 'async1') + }, + }) + const Async2 = defineAsyncComponent({ + render() { + return h('div', 'async2') + }, + }) + const components = [Async1, Async2] + const viewRef = ref(0) + const root = nodeOps.createElement('div') + const App = { + render() { + return h(KeepAlive, null, { + default: () => { + return h(Suspense, null, { + default: h(components[viewRef.value]), + fallback: h('div', 'loading'), + }) + }, + }) + }, + } + render(h(App), root) + expect(serializeInner(root)).toBe(`
loading
`) + + // switch to Async2 before Async1 resolves + viewRef.value = 1 + await nextTick() + expect(serializeInner(root)).toBe(`
loading
`) + + await Promise.all(deps) + await nextTick() + expect(serializeInner(root)).toBe('
async2
') + + viewRef.value = 0 + await nextTick() + await Promise.all(deps) + expect(serializeInner(root)).toBe(`
async1
`) + + viewRef.value = 1 + await nextTick() + await Promise.all(deps) + expect(serializeInner(root)).toBe(`
async2
`) + }) + // #6416 follow up / #10017 test('Suspense patched during HOC async component re-mount', async () => { const key = ref('k') diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index db6088cf5c6..37084d5f37a 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -228,7 +228,15 @@ const KeepAliveImpl: ComponentOptions = { const cacheSubtree = () => { // fix #1621, the pendingCacheKey could be 0 if (pendingCacheKey != null) { - cache.set(pendingCacheKey, getInnerChild(instance.subTree)) + // if KeepAlive child is a Suspense, it needs to be cached after Suspense resolves + // avoid caching vnode that not been mounted + if (isSuspense(instance.subTree.type)) { + queuePostRenderEffect(() => { + cache.set(pendingCacheKey!, getInnerChild(instance.subTree)) + }, instance.subTree.suspense) + } else { + cache.set(pendingCacheKey, getInnerChild(instance.subTree)) + } } } onMounted(cacheSubtree)