A Detailed Guide to Vue keep-alive and Its Caching Mechanism
Introduction
In Vue projects, some components or pages do not need to be rendered repeatedly. In those cases, it is useful to keep part of the UI alive in memory. This is not just simple data persistence. It is persistence of the entire component instance, including both state and view structure.
Vue provides the built-in <keep-alive> component for exactly this purpose.
When <keep-alive> wraps dynamic components, inactive component instances are cached instead of destroyed. Like <transition>, it is an abstract component: it does not render a DOM element of its own and does not appear in the parent chain the way a normal component does.
When a component is switched inside <keep-alive>, the activated and deactivated lifecycle hooks will run.
Basic usage
There are two common eras of usage.
Before Vue 2.1.0, people often used a pattern like this:
<keep-alive>
<router-view v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view v-if="!$route.meta.keepAlive"></router-view>
new Router({
routes: [
{
name: "a",
path: "/a",
component: A,
meta: {
keepAlive: true,
},
},
{
name: "b",
path: "/b",
component: B,
},
],
});After configuring route meta like this, route a gets cached while route b does not. Any route that should be cached can simply add keepAlive: true.
After Vue 2.1.0, <keep-alive> introduced two useful props:
include: only matching components are cachedexclude: matching components are not cached, and this takes priority overinclude
Both props support:
- comma-separated strings
- regular expressions
- arrays
When using regexes or arrays, v-bind is required.
Examples:
<!-- comma-separated string -->
<keep-alive include="a,b">
<component :is="view"></component>
</keep-alive>
<!-- regular expression -->
<keep-alive :include="/a|b/">
<component :is="view"></component>
</keep-alive>
<!-- array -->
<keep-alive :include="['a', 'b']">
<component :is="view"></component>
</keep-alive>
<!-- with router-view -->
<template>
<div id="app">
<transition :name="routerTransition">
<keep-alive :include="keepAliveComponentsData">
<router-view :key="$route.fullPath"></router-view>
</keep-alive>
</transition>
</div>
</template>The post-2.1.0 approach is cleaner and gives you more control over caching strategy.
Advanced usage
Basic usage is only the beginning. Real projects often need a more intentional caching strategy. The key question becomes: how can components dynamically decide whether they should be cached?
Business scenario
Imagine this flow:
- the user enters a list page
- then opens a detail page
- from the detail page, the user opens a row-level detail page
- when returning, the detail page should sometimes stay cached
- but if the row detail changed data, the detail page should refresh
This is common in mobile applications.
One workaround is to pass flags around and decide whether to use store data or refetch fresh data. That works, but it becomes awkward and does not provide real page-level caching.
If you use keep-alive carefully, the flow becomes much cleaner.
Overall strategy
- Create three store methods:
setKeepAlivesetNoKeepAlivegetKeepAlive
- Collect the
nameof every component and store them in an array - Read the cached-component list when
App.vuemounts - Dynamically decide whether pages should be cached inside route guards or page transitions
- Watch store changes in
App.vueand sync them toinclude
Concrete implementation
1. Register store methods
const state = {
keepAliveComponents: [],
};
const getters = {
getKeepAlive(state) {
return state.keepAliveComponents;
},
};
const mutations = {
setKeepAlive(state, component) {
!state.keepAliveComponents.includes(component) &&
state.keepAliveComponents.push(component);
},
setNoKeepAlive(state, component) {
const index = state.keepAliveComponents.indexOf(component);
index !== -1 && state.keepAliveComponents.splice(index, 1);
},
};
const actions = {};
export default {
state,
getters,
mutations,
actions,
};2. Default components to cached in route config
routes.forEach((item) => {
store.commit("setKeepAlive", item.name);
});
export default routes;Note: this uses item.name instead of item.component.name.
In principle, include matches component names. But when code-splitting and bundling are involved, component names can change unexpectedly. In practice, it is safer to keep route names and component names aligned from the start.
3. Read cached components in App.vue
<template>
<div id="app">
<transition :name="routerTransition">
<keep-alive :include="keepAliveComponentsData">
<router-view :key="$route.fullPath"></router-view>
</keep-alive>
</transition>
</div>
</template>
<script>
import store from "./store";
export default {
name: "App",
data() {
return {
keepAliveComponentsData: [],
};
},
mounted() {
this.keepAliveComponentsData = store.getters.getKeepAlive;
},
};
</script>4. Dynamically change caching behavior
If you jump from a detail page into a row-detail page, you may not want the row-detail page cached:
toLoanApplicationDetail(index) {
store.commit("setNoKeepAlive", "LoanlineReadonly");
}If you then jump from row detail into another associated document and want to preserve the row detail page, you can cache it again:
toContractDetail(item) {
store.commit("setKeepAlive", "LoanlineReadonly");
}5. Watch cache changes
Now you need to keep the include list in sync with the store:
<template>
<div id="app">
<transition :name="routerTransition">
<keep-alive :include="keepAliveComponentsData">
<router-view :key="$route.fullPath"></router-view>
</keep-alive>
</transition>
</div>
</template>
<script>
import store from "./store";
export default {
name: "App",
data() {
return {
keepAliveComponentsData: [],
};
},
mounted() {
this.keepAliveComponentsData = store.getters.getKeepAlive;
},
watch: {
$route() {
this.keepAliveComponentsData = store.getters.getKeepAlive;
},
},
};
</script>How it works internally
The core idea of keep-alive is to cache components as VNodes and reuse them when they match the include / exclude rules.
If the rules change and a cached VNode no longer matches, it gets destroyed.
Source-level explanation
At a high level:
createdinitializes acacheobject to hold cached VNodesdestroyedclears cached component instancesrender:- reads the first child component
- gets its component name
- checks
include/exclude - if it matches, tries to reuse a cached instance
- otherwise stores the new vnode in cache
- watchers on
includeandexcludeprune the cache when rules change
A simpler summary
createdbuilds the cache containerrenderdecides whether to reuse or cache the current component vnodeincludeandexcludedetermine which names are allowed to stay cached- cached entries are vnode-based, not direct DOM snapshots
- when the component is needed again, Vue reuses the cached vnode instance instead of recreating it
That is why keep-alive can preserve component state and view state so effectively.