From a6b1394215fe6c450224bd5ab28034bdbdca817b Mon Sep 17 00:00:00 2001 From: daiwei Date: Sat, 11 May 2024 17:05:35 +0800 Subject: [PATCH 1/4] fix(KeepAlive): properly cache nested Suspense subtree --- .../runtime-core/__tests__/components/Suspense.spec.ts | 2 +- packages/runtime-core/src/components/KeepAlive.ts | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts index a448972e139..8aa24f16b68 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() diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index db6088cf5c6..2572f7b6d44 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -228,7 +228,14 @@ const KeepAliveImpl: ComponentOptions = { const cacheSubtree = () => { // fix #1621, the pendingCacheKey could be 0 if (pendingCacheKey != null) { - cache.set(pendingCacheKey, getInnerChild(instance.subTree)) + if (isSuspense(instance.subTree.type)) { + const cacheKey = pendingCacheKey + queuePostRenderEffect(() => { + cache.set(cacheKey, getInnerChild(instance.subTree)) + }, instance.subTree.suspense) + } else { + cache.set(pendingCacheKey, getInnerChild(instance.subTree)) + } } } onMounted(cacheSubtree) From c50841b255ea280bbf146497f4e05808a55fcac1 Mon Sep 17 00:00:00 2001 From: edison1105 Date: Sat, 11 May 2024 21:28:31 +0800 Subject: [PATCH 2/4] test: add test case --- .../__tests__/components/Suspense.spec.ts | 50 +++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/packages/runtime-core/__tests__/components/Suspense.spec.ts b/packages/runtime-core/__tests__/components/Suspense.spec.ts index 8aa24f16b68..8de5b3182d0 100644 --- a/packages/runtime-core/__tests__/components/Suspense.spec.ts +++ b/packages/runtime-core/__tests__/components/Suspense.spec.ts @@ -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') From 9474f79bae6a9af610a499e119531f53f3652b17 Mon Sep 17 00:00:00 2001 From: edison1105 Date: Sat, 11 May 2024 21:41:32 +0800 Subject: [PATCH 3/4] chore: add comments --- packages/runtime-core/src/components/KeepAlive.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index 2572f7b6d44..7bf5551cb4d 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -228,6 +228,8 @@ const KeepAliveImpl: ComponentOptions = { const cacheSubtree = () => { // fix #1621, the pendingCacheKey could be 0 if (pendingCacheKey != null) { + // 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)) { const cacheKey = pendingCacheKey queuePostRenderEffect(() => { From 09d57126b8f834376e6931dfc13f4ebb274bc679 Mon Sep 17 00:00:00 2001 From: edison Date: Fri, 24 May 2024 09:25:36 +0800 Subject: [PATCH 4/4] Update KeepAlive.ts --- packages/runtime-core/src/components/KeepAlive.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/runtime-core/src/components/KeepAlive.ts b/packages/runtime-core/src/components/KeepAlive.ts index 7bf5551cb4d..37084d5f37a 100644 --- a/packages/runtime-core/src/components/KeepAlive.ts +++ b/packages/runtime-core/src/components/KeepAlive.ts @@ -231,9 +231,8 @@ const KeepAliveImpl: ComponentOptions = { // 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)) { - const cacheKey = pendingCacheKey queuePostRenderEffect(() => { - cache.set(cacheKey, getInnerChild(instance.subTree)) + cache.set(pendingCacheKey!, getInnerChild(instance.subTree)) }, instance.subTree.suspense) } else { cache.set(pendingCacheKey, getInnerChild(instance.subTree))