Skip to content

Commit fe6a26b

Browse files
committed
keep-alive: prune cache on include/exclude change, also avoid firing deactivate for already inactive components (fix #4633)
1 parent ee6ad6a commit fe6a26b

File tree

3 files changed

+79
-13
lines changed

3 files changed

+79
-13
lines changed

src/core/components/keep-alive.js

Lines changed: 48 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@ import { getFirstComponentChild } from 'core/vdom/helpers/index'
55

66
const patternTypes = [String, RegExp]
77

8+
function getComponentName (opts: ?VNodeComponentOptions): ?string {
9+
return opts && (opts.Ctor.options.name || opts.tag)
10+
}
11+
812
function matches (pattern: string | RegExp, name: string): boolean {
913
if (typeof pattern === 'string') {
1014
return pattern.split(',').indexOf(name) > -1
@@ -13,22 +17,62 @@ function matches (pattern: string | RegExp, name: string): boolean {
1317
}
1418
}
1519

20+
function pruneCache (cache, filter) {
21+
for (const key in cache) {
22+
const cachedNode = cache[key]
23+
if (cachedNode) {
24+
const name = getComponentName(cachedNode.componentOptions)
25+
if (name && !filter(name)) {
26+
pruneCacheEntry(cachedNode)
27+
cache[key] = null
28+
}
29+
}
30+
}
31+
}
32+
33+
function pruneCacheEntry (vnode: ?MountedComponentVNode) {
34+
if (vnode) {
35+
if (!vnode.componentInstance._inactive) {
36+
callHook(vnode.componentInstance, 'deactivated')
37+
}
38+
vnode.componentInstance.$destroy()
39+
}
40+
}
41+
1642
export default {
1743
name: 'keep-alive',
1844
abstract: true,
45+
1946
props: {
2047
include: patternTypes,
2148
exclude: patternTypes
2249
},
50+
2351
created () {
2452
this.cache = Object.create(null)
2553
},
54+
55+
destroyed () {
56+
for (const key in this.cache) {
57+
pruneCacheEntry(this.cache[key])
58+
}
59+
},
60+
61+
watch: {
62+
include (val: string | RegExp) {
63+
pruneCache(this.cache, name => matches(val, name))
64+
},
65+
exclude (val: string | RegExp) {
66+
pruneCache(this.cache, name => !matches(val, name))
67+
}
68+
},
69+
2670
render () {
2771
const vnode: VNode = getFirstComponentChild(this.$slots.default)
28-
if (vnode && vnode.componentOptions) {
29-
const opts: VNodeComponentOptions = vnode.componentOptions
72+
const componentOptions = vnode && vnode.componentOptions
73+
if (componentOptions) {
3074
// check pattern
31-
const name = opts.Ctor.options.name || opts.tag
75+
const name = getComponentName(componentOptions)
3276
if (name && (
3377
(this.include && !matches(this.include, name)) ||
3478
(this.exclude && matches(this.exclude, name))
@@ -38,7 +82,7 @@ export default {
3882
const key = vnode.key == null
3983
// same constructor may get registered as different local components
4084
// so cid alone is not enough (#3269)
41-
? opts.Ctor.cid + (opts.tag ? `::${opts.tag}` : '')
85+
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
4286
: vnode.key
4387
if (this.cache[key]) {
4488
vnode.componentInstance = this.cache[key].componentInstance
@@ -48,12 +92,5 @@ export default {
4892
vnode.data.keepAlive = true
4993
}
5094
return vnode
51-
},
52-
destroyed () {
53-
for (const key in this.cache) {
54-
const vnode = this.cache[key]
55-
callHook(vnode.componentInstance, 'deactivated')
56-
vnode.componentInstance.$destroy()
57-
}
5895
}
5996
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export default {
7373
name: 'transition',
7474
props: transitionProps,
7575
abstract: true,
76+
7677
render (h: Function) {
7778
let children = this.$slots.default
7879
if (!children) {

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ describe('Component keep-alive', () => {
7777
vm.ok = false // teardown
7878
}).then(() => {
7979
expect(vm.$el.textContent).toBe('')
80-
assertHookCalls(one, [1, 1, 2, 3, 1])
80+
assertHookCalls(one, [1, 1, 2, 2, 1])
8181
assertHookCalls(two, [1, 1, 2, 2, 1])
8282
}).then(done)
8383
})
@@ -104,7 +104,7 @@ describe('Component keep-alive', () => {
104104
vm.ok = false // teardown
105105
}).then(() => {
106106
expect(vm.$el.textContent).toBe('')
107-
assertHookCalls(one, [1, 1, 2, 3, 1])
107+
assertHookCalls(one, [1, 1, 2, 2, 1])
108108
assertHookCalls(two, [2, 2, 0, 0, 2])
109109
}).then(done)
110110
}
@@ -199,6 +199,34 @@ describe('Component keep-alive', () => {
199199
sharedAssertions(vm, done)
200200
})
201201

202+
it('prune cache on include/exclude change', done => {
203+
const vm = new Vue({
204+
template: `
205+
<div>
206+
<keep-alive :include="include">
207+
<component :is="view"></component>
208+
</keep-alive>
209+
</div>
210+
`,
211+
data: {
212+
view: 'one',
213+
include: 'one,two'
214+
},
215+
components
216+
}).$mount()
217+
218+
vm.view = 'two'
219+
waitForUpdate(() => {
220+
assertHookCalls(one, [1, 1, 1, 1, 0])
221+
assertHookCalls(two, [1, 1, 1, 0, 0])
222+
vm.include = 'two'
223+
vm.view = 'one'
224+
}).then(() => {
225+
assertHookCalls(one, [2, 2, 1, 1, 1])
226+
assertHookCalls(two, [1, 1, 1, 1, 0])
227+
}).then(done)
228+
})
229+
202230
// #3882
203231
it('deeply nested keep-alive should be destroyed properly', done => {
204232
one.template = `<div><keep-alive><two></two></keep-alive></div>`

0 commit comments

Comments
 (0)