浏览代码

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

Evan You 9 年之前
父节点
当前提交
fe6a26bb9c

+ 48 - 11
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 - 0
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) {

+ 30 - 2
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>`