mirror of https://github.com/vuejs/vue.git
fix(keep-alive): cache what is really needed not the whole VNode data (#12015)
Co-authored-by: zrh122 <1229550935@qq.com>
This commit is contained in:
parent
2b93e86aa1
commit
e7baaa1205
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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 = []
|
||||
|
@ -72,6 +98,7 @@ export default {
|
|||
},
|
||||
|
||||
mounted () {
|
||||
this.cacheVNode()
|
||||
this.$watch('include', val => {
|
||||
pruneCache(this, name => matches(val, name))
|
||||
})
|
||||
|
@ -80,6 +107,10 @@ export default {
|
|||
})
|
||||
},
|
||||
|
||||
updated () {
|
||||
this.cacheVNode()
|
||||
},
|
||||
|
||||
render () {
|
||||
const slot = this.$slots.default
|
||||
const vnode: VNode = getFirstComponentChild(slot)
|
||||
|
@ -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
|
||||
|
|
|
@ -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>`
|
||||
|
|
Loading…
Reference in New Issue