Browse Source

keep-alive: prune cache on include/exclude change, also avoid firing deactivate for already inactive components (fix #4633)

dev
Evan You 8 years ago
parent
commit
fe6a26bb9c
  1. 59
      src/core/components/keep-alive.js
  2. 1
      src/platforms/web/runtime/components/transition.js
  3. 32
      test/unit/features/component/component-keep-alive.spec.js

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

@ -5,6 +5,10 @@ import { getFirstComponentChild } from 'core/vdom/helpers/index'
const patternTypes = [String, RegExp]
function getComponentName (opts: ?VNodeComponentOptions): ?string {
return opts && (opts.Ctor.options.name || opts.tag)
}
function matches (pattern: string | RegExp, name: string): boolean {
if (typeof pattern === 'string') {
return pattern.split(',').indexOf(name) > -1
@ -13,22 +17,62 @@ function matches (pattern: string | RegExp, name: string): boolean {
}
}
function pruneCache (cache, filter) {
for (const key in cache) {
const cachedNode = cache[key]
if (cachedNode) {
const name = getComponentName(cachedNode.componentOptions)
if (name && !filter(name)) {
pruneCacheEntry(cachedNode)
cache[key] = null
}
}
}
}
function pruneCacheEntry (vnode: ?MountedComponentVNode) {
if (vnode) {
if (!vnode.componentInstance._inactive) {
callHook(vnode.componentInstance, 'deactivated')
}
vnode.componentInstance.$destroy()
}
}
export default {
name: 'keep-alive',
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes
},
created () {
this.cache = Object.create(null)
},
destroyed () {
for (const key in this.cache) {
pruneCacheEntry(this.cache[key])
}
},
watch: {
include (val: string | RegExp) {
pruneCache(this.cache, name => matches(val, name))
},
exclude (val: string | RegExp) {
pruneCache(this.cache, name => !matches(val, name))
}
},
render () {
const vnode: VNode = getFirstComponentChild(this.$slots.default)
if (vnode && vnode.componentOptions) {
const opts: VNodeComponentOptions = vnode.componentOptions
const componentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// check pattern
const name = opts.Ctor.options.name || opts.tag
const name = getComponentName(componentOptions)
if (name && (
(this.include && !matches(this.include, name)) ||
(this.exclude && matches(this.exclude, name))
@ -38,7 +82,7 @@ export default {
const key = vnode.key == null
// same constructor may get registered as different local components
// so cid alone is not enough (#3269)
? opts.Ctor.cid + (opts.tag ? `::${opts.tag}` : '')
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (this.cache[key]) {
vnode.componentInstance = this.cache[key].componentInstance
@ -48,12 +92,5 @@ export default {
vnode.data.keepAlive = true
}
return vnode
},
destroyed () {
for (const key in this.cache) {
const vnode = this.cache[key]
callHook(vnode.componentInstance, 'deactivated')
vnode.componentInstance.$destroy()
}
}
}

1
src/platforms/web/runtime/components/transition.js

@ -73,6 +73,7 @@ export default {
name: 'transition',
props: transitionProps,
abstract: true,
render (h: Function) {
let children = this.$slots.default
if (!children) {

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

@ -77,7 +77,7 @@ describe('Component keep-alive', () => {
vm.ok = false // teardown
}).then(() => {
expect(vm.$el.textContent).toBe('')
assertHookCalls(one, [1, 1, 2, 3, 1])
assertHookCalls(one, [1, 1, 2, 2, 1])
assertHookCalls(two, [1, 1, 2, 2, 1])
}).then(done)
})
@ -104,7 +104,7 @@ describe('Component keep-alive', () => {
vm.ok = false // teardown
}).then(() => {
expect(vm.$el.textContent).toBe('')
assertHookCalls(one, [1, 1, 2, 3, 1])
assertHookCalls(one, [1, 1, 2, 2, 1])
assertHookCalls(two, [2, 2, 0, 0, 2])
}).then(done)
}
@ -199,6 +199,34 @@ describe('Component keep-alive', () => {
sharedAssertions(vm, done)
})
it('prune cache on include/exclude change', done => {
const vm = new Vue({
template: `
<div>
<keep-alive :include="include">
<component :is="view"></component>
</keep-alive>
</div>
`,
data: {
view: 'one',
include: 'one,two'
},
components
}).$mount()
vm.view = 'two'
waitForUpdate(() => {
assertHookCalls(one, [1, 1, 1, 1, 0])
assertHookCalls(two, [1, 1, 1, 0, 0])
vm.include = 'two'
vm.view = 'one'
}).then(() => {
assertHookCalls(one, [2, 2, 1, 1, 1])
assertHookCalls(two, [1, 1, 1, 1, 0])
}).then(done)
})
// #3882
it('deeply nested keep-alive should be destroyed properly', done => {
one.template = `<div><keep-alive><two></two></keep-alive></div>`

Loading…
Cancel
Save