Skip to content

Commit 07764fe

Browse files
authored
fix(KeepAlive): properly cache nested Suspense subtree (#10912)
* fix(KeepAlive): properly cache nested Suspense subtree * test: add test case * chore: add comments * Update KeepAlive.ts
1 parent bbb5be2 commit 07764fe

File tree

2 files changed

+60
-2
lines changed

2 files changed

+60
-2
lines changed

packages/runtime-core/__tests__/components/Suspense.spec.ts

+51-1
Original file line numberDiff line numberDiff line change
@@ -2021,7 +2021,7 @@ describe('Suspense', () => {
20212021
viewRef.value = 0
20222022
await nextTick()
20232023

2024-
expect(serializeInner(root)).toBe('<!---->')
2024+
expect(serializeInner(root)).toBe('<div>sync</div>')
20252025

20262026
await Promise.all(deps)
20272027
await nextTick()
@@ -2035,6 +2035,56 @@ describe('Suspense', () => {
20352035
expect(serializeInner(root)).toBe(`<div>sync</div>`)
20362036
})
20372037

2038+
// #10899
2039+
test('KeepAlive + Suspense switch before branch resolves', async () => {
2040+
const Async1 = defineAsyncComponent({
2041+
render() {
2042+
return h('div', 'async1')
2043+
},
2044+
})
2045+
const Async2 = defineAsyncComponent({
2046+
render() {
2047+
return h('div', 'async2')
2048+
},
2049+
})
2050+
const components = [Async1, Async2]
2051+
const viewRef = ref(0)
2052+
const root = nodeOps.createElement('div')
2053+
const App = {
2054+
render() {
2055+
return h(KeepAlive, null, {
2056+
default: () => {
2057+
return h(Suspense, null, {
2058+
default: h(components[viewRef.value]),
2059+
fallback: h('div', 'loading'),
2060+
})
2061+
},
2062+
})
2063+
},
2064+
}
2065+
render(h(App), root)
2066+
expect(serializeInner(root)).toBe(`<div>loading</div>`)
2067+
2068+
// switch to Async2 before Async1 resolves
2069+
viewRef.value = 1
2070+
await nextTick()
2071+
expect(serializeInner(root)).toBe(`<div>loading</div>`)
2072+
2073+
await Promise.all(deps)
2074+
await nextTick()
2075+
expect(serializeInner(root)).toBe('<div>async2</div>')
2076+
2077+
viewRef.value = 0
2078+
await nextTick()
2079+
await Promise.all(deps)
2080+
expect(serializeInner(root)).toBe(`<div>async1</div>`)
2081+
2082+
viewRef.value = 1
2083+
await nextTick()
2084+
await Promise.all(deps)
2085+
expect(serializeInner(root)).toBe(`<div>async2</div>`)
2086+
})
2087+
20382088
// #6416 follow up / #10017
20392089
test('Suspense patched during HOC async component re-mount', async () => {
20402090
const key = ref('k')

packages/runtime-core/src/components/KeepAlive.ts

+9-1
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,15 @@ const KeepAliveImpl: ComponentOptions = {
228228
const cacheSubtree = () => {
229229
// fix #1621, the pendingCacheKey could be 0
230230
if (pendingCacheKey != null) {
231-
cache.set(pendingCacheKey, getInnerChild(instance.subTree))
231+
// if KeepAlive child is a Suspense, it needs to be cached after Suspense resolves
232+
// avoid caching vnode that not been mounted
233+
if (isSuspense(instance.subTree.type)) {
234+
queuePostRenderEffect(() => {
235+
cache.set(pendingCacheKey!, getInnerChild(instance.subTree))
236+
}, instance.subTree.suspense)
237+
} else {
238+
cache.set(pendingCacheKey, getInnerChild(instance.subTree))
239+
}
232240
}
233241
}
234242
onMounted(cacheSubtree)

0 commit comments

Comments
 (0)