codejump

AcademyVue

Performance Patterns: KeepAlive & v-memo

KeepAlive caches a component instance instead of destroying it; v-memo skips a re-render entirely when nothing it depends on changed.

2 min readUpdated

3 exercises

Waiting at the end of this page.

v-if between components destroys whichever one you switch away from

vue
<template>
  <Profile v-if="tab === 'profile'" />
  <Settings v-else />

Switching tab does not hide the inactive component — it unmounts it entirely. Any local state Settings was holding (an unsaved draft, a scroll position, an expanded section) is gone the moment tab changes away from it, and switching back mounts a brand-new instance from scratch.

KeepAlive caches the instance instead of destroying it

vue
<template>
  <KeepAlive>
    <Profile v-if="tab === 'profile'" />
    <Settings v-else />
  </KeepAlive>
</template>

Wrapped in <KeepAlive>, switching away detaches the inactive component instead of destroying it — its state, DOM and all, held in memory — and switching back reattaches the exact same instance, state intact. Two additional lifecycle hooks exist specifically for this: onActivated and onDeactivated fire on the cached component each time it is reattached or detached, distinct from onMounted/onUnmounted, which for a kept-alive component only fire once each, on its very first mount and its eventual real destruction (when it is no longer even a possible branch, not just temporarily inactive).

<KeepAlive> trades memory for state preservation — every cached instance stays alive, so it is a deliberate choice for views genuinely worth preserving (a tab with a draft, a multi-step wizard), not a default to wrap everything in.

v-memo skips re-rendering a subtree when nothing in its list changed

vue
<li v-for="item in todos" :key="item.id" v-memo="[item.id, item.done]">
  {{ item.done ? 'Done' : 'Pending' }}
</li>

Before patching this <li>, Vue compares the current [item.id, item.done] against the values from the last render. If both are unchanged, it skips re-rendering that entire subtree — a real optimization for large lists where most rows are unaffected by whatever caused the parent to re-render.

The array is a list of every value the memoized subtree's output actually depends on — not a list of "things that identify this row." v-memo="[item.id]" alone, on a row whose text depends on item.done, means Vue happily skips a render where item.done changed, because the one value in the array (item.id) never did. The bug is invisible in the exact same way as an under-specified watch source list: the code runs, nothing throws, and the specific update that should have happened silently does not.

Both are opt-in, and both cost something to use wrong

Neither <KeepAlive> nor v-memo is free: <KeepAlive> holds memory for every cached instance, and a wrong v-memo array does not just fail to optimize — it actively suppresses correct updates. Reach for either once a specific, measured cost justifies it, not as a default habit on every list or every conditional component.

What interviewers check

Whether a v-memo array gets audited for completeness, not just presence — "is everything the template reads inside there?" is the one question that catches this bug on sight, and it is the same question watchEffect exists to make automatic elsewhere.

Now practice it

1 quiz · 2 drills

Reading this page is the cheap half. These are the exercises that make you use it.