Subscribe on changes!

Kept alive components still update (renders, watchers) while being deactivated

avatar
Feb 9th 2022

Version

3.2.30

Reproduction link

sfc.vuejs.org/

Steps to reproduce

  • Click increment
  • Click change page
  • Click increment
  • Observe the logs

What is expected?

  • Logs of One and then logs for Two (only)

What is actually happening?

After changing the page once, the Page One keeps rendering while being inactive


This has been confusing to many people but I couldn't find if this behavior is expected or not

This also means watchers are executed in deactivated branches

It also worth noting that the component being deactivated renders before being deactivated. Making it impossible to skip the render with likely incorrect data.

avatar
Feb 28th 2023

I couldn't find if this behavior is expected or not

Can it be confirmed at least that these behaviour are indeed bugs and not intended?

From the layman's perspective they clearly are by the token that if someone wanted to keep the components rendering and watchers running they could use v-show to show/hide the component. The whole point of <KeepAlive> is to save resources in two ways: 1.) Avoid loosing the expensive/important state of the component, when switching away from it, as opposed to v-if and the like. 2.) Avoid taking resources for rendering and watching while in the background, as opposed to v-show="false".

The current behaviour matches that of option 2. Since it is already achievable quite easily with the v-show directive it suggest that it cannot be the goal of <KeepAlive>.

The documentation is rather vague on what the intended behaviour should be as it only really explains the behaviour as "cached". It explicitly says that the component state should be preserved (differentiating it from v-if etc.), but nothing about rendering, watchers or computed properties, which would differentiate it from v-show.

For me it looks like rendering, watchers or computed properties should be paused, otherwise we could simply just use v-show="false".

Correct me, if I'm wrong pls!

avatar
Mar 1st 2023

The current behaviour matches that of option 2. Since it is already achievable quite easily with the v-show directive it suggest that it cannot be the goal of <KeepAlive>.

From my perspective, the rendering is not taken place as it's not rendered in the DOM tree, so it's not the same as v-show="false". While the official guide said below, I would rather say it's similar to v-show, but it won't stay in the DOM tree which means there will be less DOM manipulation(especially, if we are rendering large scale table, there will be a ton of DOM manipulation). Any maybe that's why it's said cache in the official guide. (cached in the virtual DOM, maybe...)

The difference is that an element with v-show will always be rendered and remain in the DOM; v-show only toggles the display CSS property of the element.

I also created a minimal playground locally but with Vue 2. From what I can tell, it works exactly the same as what @posva see in his playground.

So I suspect maybe the behavior is intended and not a bug. Let me know if you have different ideas. :-)

avatar
Mar 1st 2023

Hello,

So I suspect maybe the behavior is intended and not a bug. Let me know if you have different ideas. :-)

Indeed if it's the same behavior as vue 2 then it might be intented, The problem is other issues showing clearly not expected behaviors have been closed in favor of this issue : https://github.com/vuejs/core/issues/5323 https://github.com/vuejs/core/issues/5207

avatar
Mar 1st 2023

I would rather say it's similar to v-show, but it won't stay in the DOM tree which means there will be less DOM manipulation

That is a good point. Thanks!

If I look from this perspective than I can see these activity levels a component in theory could be in:

Level Component state Calculated, Watch etc. Rendering In the DOM Visible Current solution
1st 💡 In memory 🔆 Run ✅ Do ✅ Yes ✅ Yes ✅ Built in: Base state of mounted, active component
2nd 💡 In memory 🔆 Run ✅ Do ✅ Yes ❌ No ✅ Built in: v-show="false"
3rd 💡 In memory 🔆 Run ✅ Do ❌ No ❌ No ✅ Built in: <KeepAlive> + v-if="false"
4th 💡 In memory 🔆 Run ⏸ Paused ❌ No ❌ No ❗️ Inconvenient1: v-memo
5th 💡 In memory 💤 Sleep ⏸ Paused ❌ No ❌ No ‼️ Almost practically impossible2: v-memo + abortable watchers
6th 💣 Destroyed 🕳 N/A 🕳 N/A ❌ No ❌ No ✅ Built in: v-if="false"

note 1: Achieving rendering to halt using v-memo can be a bit cumbersome: one can make a Ref updated by a watcher that hashes together all the attributes that would be passed in the v-memo array, but also observes a control variable which can be used to abort any update to the v-memo control Ref when false effectively halting the rendering. It would be still much more convenient to have a simple wrapper, like <KeepRunning> + v-if="false", because the approach with v-memo forces the developer to explicitly collect and specify all dependencies, which is tiresome, prone to errors and creates maintenance overhead.

note 2: The even bigger problem is: sometimes we need 5th state as well, but currently it is not achievable in any reasonably simple ways. In fact one can replace all calculated variables with watchers and make those watchers observe a bail-out variable to abort any updates when desired, but it is a huge overhead to do it this way, also it must be combined with the v-memo solution from 4th level to also pause the rendering... And I'm not even sure if that would pause calculated attributes of child components. If not, those would need to be modified as well to observe the control variable. One must be desperate to do this.

In fact the name "KeepAlive" suggests something other than 5th or 6th state, as 6th obviously cannot be considered "Alive" and 5th neither based on that Watchers, Calculated and Rendering are disabled. So implementing 3rd or 4th state under the name <KeepAlive> makes sense.

On the other hand if the explanation in the documentation says "Cached", why the component is called "Alive"? Further complicating the matters the associated component state is called "inactive", and the associated lifecycle hook is called "deactivated". I know it happens a lot that names evolve organically, but I'd suggest these 4 different names are adding to the confusion. Also the term "cached" would be the perfect terminology to differentiate 3rd and 5th state: <KeepAlive>: same as right now, <KeepCached>: same as <KeepAlive> but with rendering and watchers+calculated paused.

In an ideal world I'd love to see convenient ways to do all 6 levels:

  • 1st: ✅ available as the base mounted state
  • 2nd: ✅ available using v-show="false"
  • 3rd: ✅ <KeepAlive> + v-if="false" (related state called: inactive)
  • 4th: 🆚 Achievable, but inconvenient => Would be better to have: <KeepRunning> + v-if="false" would be huge convenience. Related state could be called: background
  • 5th: 🆚 Achievable, but very inconvenient => Would be better to have: <KeepCached> + v-if="false" and related state could be called: cached
  • 6th: ✅ available as the base destroyed state e.g. with v-if="false"
avatar
Mar 1st 2023

Now the terminology is hard to get right, because obviously existing names should be kept, also the same things may have different name from Component lifecycle perspective, documentation, developer experience representation etc.

Anyway here's my take on trying to collect my thoughts:

Aspect removed/disabled Lifecycle term Additional commonly used terminology3 Ideal state terminology4 Related markup
- mounted visible mounted -
Visible mounted visible => hidden mounted => hidden v-show="false"
In the DOM activated => deactivated active =>
inactive / cached
(any above) => alive <KeepAlive> + v-if="false"
Rendering n/a n/a (any above) => running <KeepRunning> + v-if="false"
Calculated, Watch etc. n/a n/a (any above) => cached <KeepCached> + v-if="false"
Component state * => unmounted created => destroyed (any above) => destroyed v-if="false"

note 3: These are the terms I found in Vue Dev Tools and the Documentation.

note 4: IMHO, indeed.

avatar
Sep 13th 2023

I would argue your 5th 'level' describes the most logical and expected behaviour of them all, especially when combining it with vue-router. Suppose we have a simple app, with a "home"-page and another page (let's say "about us"). We use keep-alive in combination with vue-router to keep track of the entire state of the pages. If the user is on the "about us"-page, the "home"-page is invisible and vice versa.

Suppose the user navigated from "home" to "about us". Why would any developer want:

  • calculated stuff and watchers to continue on "home"? It just takes up user's resources (e.g. cpu and ram), and we're not going to show anything to the user based on these changing values. At most it can result in unexpected behaviour.
  • rendering to continue on "home". It doesn't make sense, as the page is hidden anyways

Am I missing a very obvious use-case here? The current behaviour of keep-alive - especially in combination with vue-router - just doesn't make any sense to me at all. Can anyone enlighten me?

avatar
Sep 14th 2023

I would argue your 5h 'level' describes the most logical and expected behaviour of them all, especially when combining it with vue-router. Suppose we have a simple app, with a "home"-page and another page (let's say "about us"). We use keep-alive in combination with vue-router to keep track of the entire state of the pages. If the user is on the "about us"-page, the "home"-page is invisible and vice versa.

Suppose the user navigated from "home" to "about us". Why would any developer want:

* calculated stuff and watchers to continue on "home"? It just takes up user's resources (e.g. cpu and ram), and we're not going to show anything to the user based on these changing values. At most it can result in unexpected behaviour.

* rendering to continue on "home". It doesn't make sense, as the page is hidden anyways

Am I missing a very obvious use-case here? The current behaviour of keep-alive - especially in combination with vue-router - just doesn't make any sense to me at all. Can anyone enlighten me?

@tafelnl , I think it relates to this https://github.com/vuejs/core/pull/7286 pull request. I believe once this PR is merged. We will see this bug being fixed in vue-router soon.

avatar
Sep 18th 2023

@yxchai I think that's a totally different issue to be honest. I am talking about pausing computed and watch and pausing rendering after navigating away from a route. That issue talks about invoking the activated hook for child components

avatar
Sep 18th 2023

I tried pausing the effect in the keep-alive deactivate state in #9206. It can be achieved currently, but it is more difficult in vShow because vShow needs to execute the effect before it can determine whether to display it.