skip to content

In Vue 3, what changes when you wrap a tab's dynamic component in `<KeepAlive>` and the user leaves a half-filled long form and comes back?

level: juniorimportance: must knowfreq 58%

answer

  1. unmount versus set aside
  2. same instance comes back
  3. DOM parked off-document
  4. setup and onMounted not re-run

basics

~20 s

Without KeepAlive, Vue unmounts the form's component when the tab switches away and builds a fresh one on return, losing every typed value. KeepAlive deactivates and caches the instance instead, so the same instance, state and DOM come back.

solid answer

~40 s

Switching a `<component :is>` normally unmounts the old component: its instance, refs and DOM are destroyed, and coming back runs `setup()` again with initial state, so a half-filled form is empty. Wrapping it in `<KeepAlive>` changes what happens on switch-away: Vue **deactivates** the instance, moves its DOM into a detached container and keeps it in a cache. On return the same instance is **activated** and its DOM is moved back, so reactive state, input values and child components are exactly as the user left them. `setup()` and `onMounted` do not run again; the cached instance gets `onActivated`/`onDeactivated` instead. The price is memory for every cached instance, which `include`, `exclude` and `max` bound.

code

vue · 11 lines
vue
<script setup lang="ts">
import { ref, onMounted, onActivated } from 'vue'

const email = ref('')
onMounted(() => console.log('mounted once'))
onActivated(() => console.log('shown: first mount and every return'))
</script>

<template>
  <input v-model="email" placeholder="Email" />
</template>

go deeper

for a junior

Remember that KeepAlive keeps a switched-out component's instance and state instead of destroying it, so a form's values survive switching tabs.

for a middle

Explain deactivate versus unmount: the DOM moves to a detached container, setup and onMounted do not re-run, and onActivated/onDeactivated fire instead.

for a senior

Point out that cached instances stay live, so background work continues and memory grows; bound the cache and pause work on deactivation.

for a principal

Decide when view-local caching is the right home for state versus lifting it into a store or the URL, weighing memory, freshness and reload survival.

## The default: switching destroys A tabbed settings page often renders the active tab with a **dynamic component**, `<component :is="currentTab" />`. When `currentTab` changes, Vue treats the old component like any other element that is no longer rendered: it **unmounts** it. Unmounting destroys the component instance, its reactive state, its child components and its DOM. When the user returns, Vue creates a brand-new instance, runs `setup()` again and renders initial state. For a long form that means every field the user filled in is gone. The same is true for scroll positions inside the tab, expanded accordions and anything else held in the instance. ## What `<KeepAlive>` changes `<KeepAlive>` is a built-in component from the `vue` package. It wraps a single component child and **caches component instances instead of destroying them**: ```vue <script setup lang="ts"> import { shallowRef } from 'vue' import ProfileForm from './ProfileForm.vue' import BillingForm from './BillingForm.vue' const currentTab = shallowRef(ProfileForm) </script> <template> <button @click="currentTab = ProfileForm">Profile</button> <button @click="currentTab = BillingForm">Billing</button> <KeepAlive> <component :is="currentTab" /> </KeepAlive> </template> ``` Now the life of each tab looks different: 1. **First visit**: the component mounts normally, and because it is under `<KeepAlive>`, `onActivated` also runs right after `onMounted`. 2. **Switch away**: instead of unmounting, Vue **deactivates** the instance. Its DOM is moved into an off-document container that `<KeepAlive>` owns, and `onDeactivated` runs. 3. **Switch back**: Vue finds the instance in the cache, moves its DOM back into the page and runs `onActivated`. No new instance is created, so `setup()` and `onMounted` do not run. The typed-in values survive because the very same input elements and the very same refs are put back. ## Unmounted versus deactivated | | Without `<KeepAlive>` | With `<KeepAlive>` | |---|---|---| | Instance on switch-away | Destroyed | Kept in the cache | | DOM on switch-away | Removed and discarded | Moved to a detached container | | Hooks on switch-away | `onBeforeUnmount`, `onUnmounted` | `onDeactivated` | | Hooks on return | Full `setup()` and mount hooks | `onActivated` only | | State on return | Initial values | Exactly as left | | Memory while hidden | Freed | Held | ## What "cached" really means - The instance is **alive**, not frozen. Its reactive effects and watchers are not stopped, so timers, polling or event listeners it registered keep running while it is hidden unless you pause them in `onDeactivated`. - `onActivated` and `onDeactivated` fire for the cached component **and for its descendants**, so a child inside the form can react to being shown again too. - Only one component child may be active inside `<KeepAlive>` at a time; with several children Vue warns `KeepAlive should contain exactly one component child.` - A plain element child is rendered but never cached; caching works on component instances. ## Bounding the cache By default every component that passes through `<KeepAlive>` is cached. Three props control that: - `include`: only components whose **name** matches are cached; - `exclude`: components whose name matches are never cached; - `max`: the maximum number of cached instances; beyond it the least recently accessed one is destroyed. ## Common interview traps Interviewers often probe whether a candidate knows what KeepAlive is **not**: - It is not `v-show`. `v-show` leaves the element in place and toggles `display`; KeepAlive takes the DOM out of the page entirely and puts it back later. - It is not a persistence layer. Nothing is written to storage, and a page reload loses every cached instance. - It does not snapshot and restore values. The state survives because the **same objects** are reused, which is also why they can be stale. - It is not free. A cached instance keeps its memory and its running effects until it is evicted. Knowing these four distinctions usually matters more in an interview than reciting the prop list. ## When not to reach for it KeepAlive trades memory and freshness for preserved state. If the form's data must survive a page reload, or be shared with other views, the state belongs outside the component, and the component can stay disposable. KeepAlive is the right tool when the state is purely view-local and the cost of rebuilding it, including what the user typed, is what you want to avoid. ## Summary `<KeepAlive>` turns "switch away" from **unmount** into **deactivate**. The instance, its state and its DOM are parked and later reinserted, which is why the half-filled form comes back intact and why the lifecycle you see on return is `onActivated`, not `setup()` and `onMounted`.

  • Where do the cached component's DOM nodes live while it is hidden?
    `<KeepAlive>` owns a detached container element created off the document. On deactivation Vue moves the component's DOM into it, and on activation moves it back into the page. Nothing is re-created, which is why input values, scroll positions inside the component and child state survive.
  • Does a hidden, cached component stop reacting to state changes?
    No. Deactivation does not stop the instance's reactive effects, so its watchers and any intervals or listeners it set up keep running. That is usually what surprises people: background polling continues for a tab nobody is looking at. Pause such work in `onDeactivated` and resume it in `onActivated`.

A coat check: you hand over your coat with everything still in its pockets and get the very same coat back, rather than being issued a new one.

saying these in an interview costs you the question

  • KeepAlive re-runs setup() on return but restores the old values
  • KeepAlive hides the component with display: none, like v-show
  • Cached components are frozen, so their watchers stop while hidden
  • onMounted runs every time a cached tab is shown again
  • KeepAlive can cache several active children at once