Skip to content

Commit e7baaa1

Browse files
posvazrh122
andauthoredApr 16, 2021
fix(keep-alive): cache what is really needed not the whole VNode data (#12015)
Co-authored-by: zrh122 <1229550935@qq.com>
1 parent 2b93e86 commit e7baaa1

File tree

2 files changed

+109
-14
lines changed

2 files changed

+109
-14
lines changed
 

‎src/core/components/keep-alive.js

+42-14
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@
33
import { isRegExp, remove } from 'shared/util'
44
import { getFirstComponentChild } from 'core/vdom/helpers/index'
55

6-
type VNodeCache = { [key: string]: ?VNode };
6+
type CacheEntry = {
7+
name: ?string;
8+
tag: ?string;
9+
componentInstance: Component;
10+
};
11+
12+
type CacheEntryMap = { [key: string]: ?CacheEntry };
713

814
function getComponentName (opts: ?VNodeComponentOptions): ?string {
915
return opts && (opts.Ctor.options.name || opts.tag)
@@ -24,9 +30,9 @@ function matches (pattern: string | RegExp | Array<string>, name: string): boole
2430
function pruneCache (keepAliveInstance: any, filter: Function) {
2531
const { cache, keys, _vnode } = keepAliveInstance
2632
for (const key in cache) {
27-
const cachedNode: ?VNode = cache[key]
28-
if (cachedNode) {
29-
const name: ?string = getComponentName(cachedNode.componentOptions)
33+
const entry: ?CacheEntry = cache[key]
34+
if (entry) {
35+
const name: ?string = entry.name
3036
if (name && !filter(name)) {
3137
pruneCacheEntry(cache, key, keys, _vnode)
3238
}
@@ -35,14 +41,14 @@ function pruneCache (keepAliveInstance: any, filter: Function) {
3541
}
3642

3743
function pruneCacheEntry (
38-
cache: VNodeCache,
44+
cache: CacheEntryMap,
3945
key: string,
4046
keys: Array<string>,
4147
current?: VNode
4248
) {
43-
const cached = cache[key]
44-
if (cached && (!current || cached.tag !== current.tag)) {
45-
cached.componentInstance.$destroy()
49+
const entry: ?CacheEntry = cache[key]
50+
if (entry && (!current || entry.tag !== current.tag)) {
51+
entry.componentInstance.$destroy()
4652
}
4753
cache[key] = null
4854
remove(keys, key)
@@ -60,6 +66,26 @@ export default {
6066
max: [String, Number]
6167
},
6268

69+
methods: {
70+
cacheVNode() {
71+
const { cache, keys, vnodeToCache, keyToCache } = this
72+
if (vnodeToCache) {
73+
const { tag, componentInstance, componentOptions } = vnodeToCache
74+
cache[keyToCache] = {
75+
name: getComponentName(componentOptions),
76+
tag,
77+
componentInstance,
78+
}
79+
keys.push(keyToCache)
80+
// prune oldest entry
81+
if (this.max && keys.length > parseInt(this.max)) {
82+
pruneCacheEntry(cache, keys[0], keys, this._vnode)
83+
}
84+
this.vnodeToCache = null
85+
}
86+
}
87+
},
88+
6389
created () {
6490
this.cache = Object.create(null)
6591
this.keys = []
@@ -72,6 +98,7 @@ export default {
7298
},
7399

74100
mounted () {
101+
this.cacheVNode()
75102
this.$watch('include', val => {
76103
pruneCache(this, name => matches(val, name))
77104
})
@@ -80,6 +107,10 @@ export default {
80107
})
81108
},
82109

110+
updated () {
111+
this.cacheVNode()
112+
},
113+
83114
render () {
84115
const slot = this.$slots.default
85116
const vnode: VNode = getFirstComponentChild(slot)
@@ -109,12 +140,9 @@ export default {
109140
remove(keys, key)
110141
keys.push(key)
111142
} else {
112-
cache[key] = vnode
113-
keys.push(key)
114-
// prune oldest entry
115-
if (this.max && keys.length > parseInt(this.max)) {
116-
pruneCacheEntry(cache, keys[0], keys, this._vnode)
117-
}
143+
// delay setting the cache until update
144+
this.vnodeToCache = vnode
145+
this.keyToCache = key
118146
}
119147

120148
vnode.data.keepAlive = true

‎test/unit/features/component/component-keep-alive.spec.js

+67
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,73 @@ describe('Component keep-alive', () => {
572572
}).then(done)
573573
})
574574

575+
it('max=1', done => {
576+
const spyA = jasmine.createSpy()
577+
const spyB = jasmine.createSpy()
578+
const spyC = jasmine.createSpy()
579+
const spyAD = jasmine.createSpy()
580+
const spyBD = jasmine.createSpy()
581+
const spyCD = jasmine.createSpy()
582+
583+
function assertCount (calls) {
584+
expect([
585+
spyA.calls.count(),
586+
spyAD.calls.count(),
587+
spyB.calls.count(),
588+
spyBD.calls.count(),
589+
spyC.calls.count(),
590+
spyCD.calls.count()
591+
]).toEqual(calls)
592+
}
593+
594+
const vm = new Vue({
595+
template: `
596+
<keep-alive max="1">
597+
<component :is="n"></component>
598+
</keep-alive>
599+
`,
600+
data: {
601+
n: 'aa'
602+
},
603+
components: {
604+
aa: {
605+
template: '<div>a</div>',
606+
created: spyA,
607+
destroyed: spyAD
608+
},
609+
bb: {
610+
template: '<div>bbb</div>',
611+
created: spyB,
612+
destroyed: spyBD
613+
},
614+
cc: {
615+
template: '<div>ccc</div>',
616+
created: spyC,
617+
destroyed: spyCD
618+
}
619+
}
620+
}).$mount()
621+
622+
assertCount([1, 0, 0, 0, 0, 0])
623+
vm.n = 'bb'
624+
waitForUpdate(() => {
625+
// should prune A because max cache reached
626+
assertCount([1, 1, 1, 0, 0, 0])
627+
vm.n = 'cc'
628+
}).then(() => {
629+
// should prune B because max cache reached
630+
assertCount([1, 1, 1, 1, 1, 0])
631+
vm.n = 'bb'
632+
}).then(() => {
633+
// B is recreated
634+
assertCount([1, 1, 2, 1, 1, 1])
635+
vm.n = 'aa'
636+
}).then(() => {
637+
// B is destroyed and A recreated
638+
assertCount([2, 1, 2, 2, 1, 1])
639+
}).then(done)
640+
})
641+
575642
it('should warn unknown component inside', () => {
576643
new Vue({
577644
template: `<keep-alive><foo/></keep-alive>`

0 commit comments

Comments
 (0)
Please sign in to comment.