Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
fix(keep-alive): cache what is really needed not the whole VNode data (
…#12015)

Co-authored-by: zrh122 <1229550935@qq.com>
  • Loading branch information
posva and zrh122 committed Apr 16, 2021
1 parent 2b93e86 commit e7baaa1
Show file tree
Hide file tree
Showing 2 changed files with 109 additions and 14 deletions.
56 changes: 42 additions & 14 deletions src/core/components/keep-alive.js
Expand Up @@ -3,7 +3,13 @@
import { isRegExp, remove } from 'shared/util'
import { getFirstComponentChild } from 'core/vdom/helpers/index'

type VNodeCache = { [key: string]: ?VNode };
type CacheEntry = {
name: ?string;
tag: ?string;
componentInstance: Component;
};

type CacheEntryMap = { [key: string]: ?CacheEntry };

function getComponentName (opts: ?VNodeComponentOptions): ?string {
return opts && (opts.Ctor.options.name || opts.tag)
Expand All @@ -24,9 +30,9 @@ function matches (pattern: string | RegExp | Array<string>, name: string): boole
function pruneCache (keepAliveInstance: any, filter: Function) {
const { cache, keys, _vnode } = keepAliveInstance
for (const key in cache) {
const cachedNode: ?VNode = cache[key]
if (cachedNode) {
const name: ?string = getComponentName(cachedNode.componentOptions)
const entry: ?CacheEntry = cache[key]
if (entry) {
const name: ?string = entry.name
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode)
}
Expand All @@ -35,14 +41,14 @@ function pruneCache (keepAliveInstance: any, filter: Function) {
}

function pruneCacheEntry (
cache: VNodeCache,
cache: CacheEntryMap,
key: string,
keys: Array<string>,
current?: VNode
) {
const cached = cache[key]
if (cached && (!current || cached.tag !== current.tag)) {
cached.componentInstance.$destroy()
const entry: ?CacheEntry = cache[key]
if (entry && (!current || entry.tag !== current.tag)) {
entry.componentInstance.$destroy()
}
cache[key] = null
remove(keys, key)
Expand All @@ -60,6 +66,26 @@ export default {
max: [String, Number]
},

methods: {
cacheVNode() {
const { cache, keys, vnodeToCache, keyToCache } = this
if (vnodeToCache) {
const { tag, componentInstance, componentOptions } = vnodeToCache
cache[keyToCache] = {
name: getComponentName(componentOptions),
tag,
componentInstance,
}
keys.push(keyToCache)
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
this.vnodeToCache = null
}
}
},

created () {
this.cache = Object.create(null)
this.keys = []
Expand All @@ -72,6 +98,7 @@ export default {
},

mounted () {
this.cacheVNode()
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
Expand All @@ -80,6 +107,10 @@ export default {
})
},

updated () {
this.cacheVNode()
},

render () {
const slot = this.$slots.default
const vnode: VNode = getFirstComponentChild(slot)
Expand Down Expand Up @@ -109,12 +140,9 @@ export default {
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
// delay setting the cache until update
this.vnodeToCache = vnode
this.keyToCache = key
}

vnode.data.keepAlive = true
Expand Down
67 changes: 67 additions & 0 deletions test/unit/features/component/component-keep-alive.spec.js
Expand Up @@ -572,6 +572,73 @@ describe('Component keep-alive', () => {
}).then(done)
})

it('max=1', done => {
const spyA = jasmine.createSpy()
const spyB = jasmine.createSpy()
const spyC = jasmine.createSpy()
const spyAD = jasmine.createSpy()
const spyBD = jasmine.createSpy()
const spyCD = jasmine.createSpy()

function assertCount (calls) {
expect([
spyA.calls.count(),
spyAD.calls.count(),
spyB.calls.count(),
spyBD.calls.count(),
spyC.calls.count(),
spyCD.calls.count()
]).toEqual(calls)
}

const vm = new Vue({
template: `
<keep-alive max="1">
<component :is="n"></component>
</keep-alive>
`,
data: {
n: 'aa'
},
components: {
aa: {
template: '<div>a</div>',
created: spyA,
destroyed: spyAD
},
bb: {
template: '<div>bbb</div>',
created: spyB,
destroyed: spyBD
},
cc: {
template: '<div>ccc</div>',
created: spyC,
destroyed: spyCD
}
}
}).$mount()

assertCount([1, 0, 0, 0, 0, 0])
vm.n = 'bb'
waitForUpdate(() => {
// should prune A because max cache reached
assertCount([1, 1, 1, 0, 0, 0])
vm.n = 'cc'
}).then(() => {
// should prune B because max cache reached
assertCount([1, 1, 1, 1, 1, 0])
vm.n = 'bb'
}).then(() => {
// B is recreated
assertCount([1, 1, 2, 1, 1, 1])
vm.n = 'aa'
}).then(() => {
// B is destroyed and A recreated
assertCount([2, 1, 2, 2, 1, 1])
}).then(done)
})

it('should warn unknown component inside', () => {
new Vue({
template: `<keep-alive><foo/></keep-alive>`
Expand Down

0 comments on commit e7baaa1

Please sign in to comment.