Kept alive components still update (renders, watchers) while being deactivated
Version
3.2.30
Reproduction link
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.
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!
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 thedisplay
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. :-)
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
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"
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.
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?
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 usekeep-alive
in combination withvue-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 withvue-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.
@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