fix(keep-alive): fix keep-alive with scopeId/fallthrough attrs

fix #1511
This commit is contained in:
Evan You 2020-07-06 18:12:16 -04:00
parent 6dd59ee301
commit d86b01ba3a
2 changed files with 53 additions and 3 deletions

View File

@ -14,7 +14,8 @@ import {
ComponentPublicInstance, ComponentPublicInstance,
Ref, Ref,
cloneVNode, cloneVNode,
provide provide,
withScopeId
} from '@vue/runtime-test' } from '@vue/runtime-test'
import { KeepAliveProps } from '../../src/components/KeepAlive' import { KeepAliveProps } from '../../src/components/KeepAlive'
@ -655,4 +656,30 @@ describe('KeepAlive', () => {
expect(spyMounted).toHaveBeenCalledTimes(3) expect(spyMounted).toHaveBeenCalledTimes(3)
expect(spyUnmounted).toHaveBeenCalledTimes(4) expect(spyUnmounted).toHaveBeenCalledTimes(4)
}) })
// #1513
test('should work with cloned root due to scopeId / fallthrough attrs', async () => {
const viewRef = ref('one')
const instanceRef = ref<any>(null)
const withId = withScopeId('foo')
const App = {
__scopeId: 'foo',
render: withId(() => {
return h(KeepAlive, null, {
default: () => h(views[viewRef.value], { ref: instanceRef })
})
})
}
render(h(App), root)
expect(serializeInner(root)).toBe(`<div foo>one</div>`)
instanceRef.value.msg = 'changed'
await nextTick()
expect(serializeInner(root)).toBe(`<div foo>changed</div>`)
viewRef.value = 'two'
await nextTick()
expect(serializeInner(root)).toBe(`<div foo>two</div>`)
viewRef.value = 'one'
await nextTick()
expect(serializeInner(root)).toBe(`<div foo>changed</div>`)
})
}) })

View File

@ -9,7 +9,13 @@ import {
} from '../component' } from '../component'
import { VNode, cloneVNode, isVNode, VNodeProps } from '../vnode' import { VNode, cloneVNode, isVNode, VNodeProps } from '../vnode'
import { warn } from '../warning' import { warn } from '../warning'
import { onBeforeUnmount, injectHook, onUnmounted } from '../apiLifecycle' import {
onBeforeUnmount,
injectHook,
onUnmounted,
onBeforeMount,
onBeforeUpdate
} from '../apiLifecycle'
import { import {
isString, isString,
isArray, isArray,
@ -173,6 +179,16 @@ const KeepAliveImpl = {
} }
) )
// cache sub tree in beforeMount/Update (i.e. right after the render)
let pendingCacheKey: CacheKey | null = null
const cacheSubtree = () => {
if (pendingCacheKey) {
cache.set(pendingCacheKey, instance.subTree)
}
}
onBeforeMount(cacheSubtree)
onBeforeUpdate(cacheSubtree)
onBeforeUnmount(() => { onBeforeUnmount(() => {
cache.forEach(cached => { cache.forEach(cached => {
const { subTree, suspense } = instance const { subTree, suspense } = instance
@ -189,6 +205,8 @@ const KeepAliveImpl = {
}) })
return () => { return () => {
pendingCacheKey = null
if (!slots.default) { if (!slots.default) {
return null return null
} }
@ -227,7 +245,12 @@ const KeepAliveImpl = {
if (vnode.el) { if (vnode.el) {
vnode = cloneVNode(vnode) vnode = cloneVNode(vnode)
} }
cache.set(key, vnode) // #1513 it's possible for the returned vnode to be cloned due to attr
// fallthrough or scopeId, so the vnode here may not be the final vnode
// that is mounted. Instead of caching it directly, we store the pending
// key and cache `instance.subTree` (the normalized vnode) in
// beforeMount/beforeUpdate hooks.
pendingCacheKey = key
if (cachedVNode) { if (cachedVNode) {
// copy over mounted state // copy over mounted state