Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

<keep-alive> doesn't destroy cached components after when max is 1 #9972

Closed
kchung opened this issue May 3, 2019 · 13 comments · Fixed by #12015
Closed

<keep-alive> doesn't destroy cached components after when max is 1 #9972

kchung opened this issue May 3, 2019 · 13 comments · Fixed by #12015

Comments

@kchung
Copy link

kchung commented May 3, 2019

Version

2.6.10

Reproduction link

https://jsfiddle.net/p0te9vL3/

Steps to reproduce

  1. Open dev tools to see console
  2. Cycle through the routes
  3. View console and observe lifecycle hook logging

What is expected?

I'd expect once the max cache is reached (in the example case of 1), the component that gets pruned also gets destroyed. In the documentation it states (emphasis mine):

max

The maximum number of component instances to cache. Once this number is reached, the cached component instance that was least recently accessed will be destroyed before creating a new instance.

What is actually happening?

The pruned component is never destroyed, it's just removed from the cache. In the example, the beforeDestroy hook is never called.


Related code:

function pruneCacheEntry (
cache: VNodeCache,
key: string,
keys: Array<string>,
current?: VNode
) {
const cached = cache[key]
if (cached && (!current || cached.tag !== current.tag)) {
cached.componentInstance.$destroy()
}
cache[key] = null
remove(keys, key)
}

@posva posva changed the title <keep-alive> doesn't destroy cached components after max has been reached <keep-alive> doesn't destroy cached components after when max is 1 May 4, 2019
@posva
Copy link
Member

posva commented May 4, 2019

It seems not to destroy components when the max is one (in which case using keep-alive is pointless btw). I removed Vue Router from the repro to make it simpler: https://jsfiddle.net/p0te9vL3/ (max is set to 2 to show it works)

I marking this as a bug for consistency. The workaround would be not using a keep-alive if the value for max is 1

@await-ovo
Copy link

should we just validate max value just like this?

       if (parseInt(this.max) > 1) {
          var ref$1 = this;
          var cache = ref$1.cache;
          var keys = ref$1.keys;
          var key = vnode.key == null
            // same constructor may get registered as different local components
            // so cid alone is not enough (#3269)
            ? componentOptions.Ctor.cid + (componentOptions.tag ? ("::" + (componentOptions.tag)) : '')
            : vnode.key;
          if (cache[key]) {
            vnode.componentInstance = cache[key].componentInstance;
            // make current key freshest
            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);
            }
          }
  
          vnode.data.keepAlive = true;
        }

@kchung
Copy link
Author

kchung commented May 6, 2019

@posva maybe I'm confused by the prop, but when I used :max="1" I was expecting it to "keep alive" the previous component, so it'd always remember the trailing component but would destroy anything past 1

@posva
Copy link
Member

posva commented May 6, 2019

it will cache 1 component, so as soon as you switch the cache is occupied by the new component and the previous component is removed. A cache of one entry here is the same as not having any cache

@kchung
Copy link
Author

kchung commented May 6, 2019

@posva ah that makes more sense, thanks!

@zrh122
Copy link
Contributor

zrh122 commented May 14, 2019

@posva, #9962 already fixes it, i just found out.

@posva posva added the has PR label May 14, 2019
@posva
Copy link
Member

posva commented May 14, 2019

@zrh122 are you sure? I haven't tested but the pr #10015 introduced a very small change that does not appear in yours. In any case, it would be nice if we could merge both for different issues, so both contributions are taken into account 🙂

@zrh122
Copy link
Contributor

zrh122 commented May 14, 2019

@zrh122 are you sure? I haven't tested but the pr #10015 introduced a very small change that does not appear in yours. In any case, it would be nice if we could merge both for different issues, so both contributions are taken into account 🙂

Yes, in my code i delay to call pruneCacheEntry until component mounted or updated, at that time this._vnode is equal to variable vnode.

@unitree-czk
Copy link

unitree-czk commented Nov 22, 2019

@posva I am using keep-alive to cache some expensive page, like the following.

<keep-alive max="1"> <router-view v-if="$route.meta.needCache"></router-view> </keep-alive> <router-view v-if="!$route.meta.needCache"></router-view>

However, I find out that there is no "beforeDestroy" hook for the cached file.
I used "deactivated hook", but it seems that there is some minor memory leak.

@unitree-czk
Copy link

I wonder if can clean up nodes thoroughly....

@unitree-czk
Copy link

It seems not to destroy components when the max is one (in which case using keep-alive is pointless btw). I removed Vue Router from the repro to make it simpler: https://jsfiddle.net/p0te9vL3/ (max is set to 2 to show it works)

I marking this as a bug for consistency. The workaround would be not using a keep-alive if the value for max is 1

<keep-alive max="1"> <router-view v-if="$route.meta.needCache"></router-view> </keep-alive> <router-view v-if="!$route.meta.needCache"></router-view>
I use this to keep only one expensive page, so that user can switch from nonexpensive one to expensive one easily, but one can only cache one expensive page.

@unitree-czk
Copy link

@posva I wonder if there are any other choices for my situation.

@RichieChoo
Copy link

@zrh122 are you sure? I haven't tested but the pr #10015 introduced a very small change that does not appear in yours. In any case, it would be nice if we could merge both for different issues, so both contributions are taken into account 🙂

Yes, in my code i delay to call pruneCacheEntry until component mounted or updated, at that time this._vnode is equal to variable vnode.

It may cause other problem, such as last router will fresh when activated

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants