skip to content

In Vue 3, why can `<KeepAlive include="CheckoutForm">` fail to cache a component, and how does Vue match `include` and `exclude`?

level: middleimportance: should knowfreq 44%

answer

  1. matching is not by import
  2. the component's name option
  3. script setup infers from filename
  4. comma strings are split, not trimmed

basics

~20 s

Vue's KeepAlive matches include and exclude against the component's name option, not its import or tag. A component with no matching name, a misspelled filename-inferred name, or a spaced comma list is simply not cached.

solid answer

~40 s

`include` and `exclude` accept a comma-delimited string, a `RegExp`, or an array of either, and are matched against the child component's **name**. For a `<script setup>` SFC the name is inferred from the filename (since 3.2.34), so `CheckoutForm.vue` matches `"CheckoutForm"`; an explicit name can be set with `defineOptions({ name })` (3.3+) or the `name` option. Common failures: the file is called `checkout-form.vue`, the component is anonymous, or the list is written `"CartView, CheckoutForm"` with a space, because the string is split on commas and matched exactly. RegExp and array values need `v-bind`. When `include` or `exclude` changes at runtime, cached instances that no longer qualify are destroyed.

code

vue · 7 lines
vue
<script setup lang="ts">
defineOptions({ name: 'CheckoutForm' })
</script>

<template>
  <form>...</form>
</template>

go deeper

for a junior

Know that include and exclude choose which components KeepAlive caches, and that they refer to component names.

for a middle

Explain where the name comes from (explicit name, defineOptions, filename inference for script setup) and the exact string, RegExp and array matching rules.

for a senior

Debug the silent non-caching cases quickly and use a reactive include list to invalidate specific cached views after their data goes stale.

for a principal

Set a convention for explicit component names on anything cached, so refactors and file renames cannot quietly change caching behaviour.

## What `include` and `exclude` do By default `<KeepAlive>` caches **every** component instance that passes through it. Two props narrow that down: - `include`: only components whose name matches are cached; everything else is unmounted normally on switch-away; - `exclude`: components whose name matches are never cached; everything else is. Both take the same pattern type: a **comma-delimited string**, a **`RegExp`**, or an **array** containing strings and regular expressions. ```vue <template> <KeepAlive include="CartView,CheckoutForm"> <component :is="step" /> </KeepAlive> <KeepAlive :exclude="/^Preview/"> <component :is="panel" /> </KeepAlive> <KeepAlive :include="['CartView', /Form$/]"> <component :is="step" /> </KeepAlive> </template> ``` A plain attribute is always a string, so the RegExp and array forms must be bound with `v-bind` (`:include`). ## What the pattern is matched against The match is against the component's **name**, not the variable it was imported as and not the tag used in the template. Vue resolves the name in this order: 1. an explicit `name`, set with `defineOptions({ name: 'CheckoutForm' })` in `<script setup>` (Vue 3.3+) or with the `name` option in a normal `<script>` block; 2. for a `<script setup>` SFC with no explicit name, the name **inferred from the filename** (Vue 3.2.34+), so `CheckoutForm.vue` becomes `CheckoutForm`. For an async component created with `defineAsyncComponent`, the name checked is the one of the loaded inner component once it has resolved. ## How the string form is compared The string form is split on commas and each piece is compared to the name **exactly**. There is no trimming and no case folding: | `include` value | Name `CheckoutForm` cached? | Why | |---|---|---| | `"CartView,CheckoutForm"` | Yes | Exact piece after split | | `"CartView, CheckoutForm"` | No | The piece is `" CheckoutForm"` with a leading space | | `"checkoutform"` | No | Comparison is case-sensitive | | `"checkout-form"` | No | No kebab-to-Pascal conversion is applied | | `:include="/Form$/"` | Yes | RegExp tested against the name | ## Why a component silently is not cached When something "is not being cached", check in this order: - **No name at all.** With `include` set, a component without a resolvable name never matches, so it is not cached. Anonymous components from inline objects or render functions are typical. - **The file name is not the name you think.** `checkout-form.vue` infers `checkout-form`, which does not match `CheckoutForm`. - **A space in the comma list**, as in the table above. - **A RegExp or array written without `v-bind`**, which turns it into a literal string. - **`exclude` wins where both match**: a component matching `exclude` is not cached even if `include` also matches. The mirror case for `exclude`: a component with no name can never match `exclude`, so it **is** cached. ## Changing the rules at runtime `include` and `exclude` are reactive. `<KeepAlive>` watches them and, after the next render, prunes cached entries whose names no longer qualify; those instances are destroyed with their normal unmount hooks. That is a practical way to **invalidate** a cache: remove a name from `include` when its data becomes stale, then add it back. ```ts import { ref } from 'vue' export const cachedViews = ref<string[]>(['CartView', 'CheckoutForm']) export function dropFromCache(name: string) { cachedViews.value = cachedViews.value.filter((n) => n !== name) } ``` Binding `:include="cachedViews"` and calling `dropFromCache('CheckoutForm')` after a successful order destroys the cached form, so the next visit starts clean. ## Debugging checklist When a view is not being cached as expected, confirm the facts in the browser rather than guessing: 1. Open Vue Devtools and read the component's displayed name; that is close to what `include` will be compared with. 2. Log the bound `include` value and check for spaces, casing and kebab-case versus PascalCase. 3. If the list is dynamic, confirm it contains the name **at the moment of switching away**: a name added after the switch cannot bring back an instance that was already unmounted. 4. Check whether the same name also appears in `exclude`. Fixing these usually means adding an explicit `defineOptions({ name })` and switching to an array value, which is both typed and whitespace-proof. ## Summary KeepAlive filters by **component name**. Make names explicit or rely on filename inference deliberately, write comma lists without spaces or use arrays, and bind RegExp or array values with `v-bind`. Treat the reactive `include` list as the tool for evicting a specific cached view.

  • What happens to already cached instances when you remove a name from a bound `include` array?
    `<KeepAlive>` watches `include` and `exclude` and, after the next render, prunes cached entries whose names no longer match. Those instances are destroyed with their normal unmount hooks, so the next visit builds a fresh instance. That makes a reactive `include` list a clean way to invalidate one cached view.
  • A component matches both `include` and `exclude`; is it cached?
    No. Vue skips caching when `include` is set and the name does not match, or when `exclude` is set and the name matches. So a name present in `exclude` is never cached, whatever `include` says.

saying these in an interview costs you the question

  • include matches the tag name you wrote in the template
  • include matches the variable name the component was imported as
  • Spaces after commas in include="A, B" are ignored
  • A regular expression works as a plain include attribute without v-bind
  • Changing include at runtime has no effect on instances already cached