Skip to content

Commit d86b01b

Browse files
committed
fix(keep-alive): fix keep-alive with scopeId/fallthrough attrs
fix #1511
1 parent 6dd59ee commit d86b01b

File tree

2 files changed

+53
-3
lines changed

2 files changed

+53
-3
lines changed

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

+28-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@ import {
1414
ComponentPublicInstance,
1515
Ref,
1616
cloneVNode,
17-
provide
17+
provide,
18+
withScopeId
1819
} from '@vue/runtime-test'
1920
import { KeepAliveProps } from '../../src/components/KeepAlive'
2021

@@ -655,4 +656,30 @@ describe('KeepAlive', () => {
655656
expect(spyMounted).toHaveBeenCalledTimes(3)
656657
expect(spyUnmounted).toHaveBeenCalledTimes(4)
657658
})
659+
660+
// #1513
661+
test('should work with cloned root due to scopeId / fallthrough attrs', async () => {
662+
const viewRef = ref('one')
663+
const instanceRef = ref<any>(null)
664+
const withId = withScopeId('foo')
665+
const App = {
666+
__scopeId: 'foo',
667+
render: withId(() => {
668+
return h(KeepAlive, null, {
669+
default: () => h(views[viewRef.value], { ref: instanceRef })
670+
})
671+
})
672+
}
673+
render(h(App), root)
674+
expect(serializeInner(root)).toBe(`<div foo>one</div>`)
675+
instanceRef.value.msg = 'changed'
676+
await nextTick()
677+
expect(serializeInner(root)).toBe(`<div foo>changed</div>`)
678+
viewRef.value = 'two'
679+
await nextTick()
680+
expect(serializeInner(root)).toBe(`<div foo>two</div>`)
681+
viewRef.value = 'one'
682+
await nextTick()
683+
expect(serializeInner(root)).toBe(`<div foo>changed</div>`)
684+
})
658685
})

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

+25-2
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,13 @@ import {
99
} from '../component'
1010
import { VNode, cloneVNode, isVNode, VNodeProps } from '../vnode'
1111
import { warn } from '../warning'
12-
import { onBeforeUnmount, injectHook, onUnmounted } from '../apiLifecycle'
12+
import {
13+
onBeforeUnmount,
14+
injectHook,
15+
onUnmounted,
16+
onBeforeMount,
17+
onBeforeUpdate
18+
} from '../apiLifecycle'
1319
import {
1420
isString,
1521
isArray,
@@ -173,6 +179,16 @@ const KeepAliveImpl = {
173179
}
174180
)
175181

182+
// cache sub tree in beforeMount/Update (i.e. right after the render)
183+
let pendingCacheKey: CacheKey | null = null
184+
const cacheSubtree = () => {
185+
if (pendingCacheKey) {
186+
cache.set(pendingCacheKey, instance.subTree)
187+
}
188+
}
189+
onBeforeMount(cacheSubtree)
190+
onBeforeUpdate(cacheSubtree)
191+
176192
onBeforeUnmount(() => {
177193
cache.forEach(cached => {
178194
const { subTree, suspense } = instance
@@ -189,6 +205,8 @@ const KeepAliveImpl = {
189205
})
190206

191207
return () => {
208+
pendingCacheKey = null
209+
192210
if (!slots.default) {
193211
return null
194212
}
@@ -227,7 +245,12 @@ const KeepAliveImpl = {
227245
if (vnode.el) {
228246
vnode = cloneVNode(vnode)
229247
}
230-
cache.set(key, vnode)
248+
// #1513 it's possible for the returned vnode to be cloned due to attr
249+
// fallthrough or scopeId, so the vnode here may not be the final vnode
250+
// that is mounted. Instead of caching it directly, we store the pending
251+
// key and cache `instance.subTree` (the normalized vnode) in
252+
// beforeMount/beforeUpdate hooks.
253+
pendingCacheKey = key
231254

232255
if (cachedVNode) {
233256
// copy over mounted state

0 commit comments

Comments
 (0)