Vue 3 KeepAlive: кеш маршрутов, сохранение состояния и контроль памяти
<KeepAlive> — встроенный компонент Vue для кеширования неактивных экземпляров компонентов. Он полезен, когда пользователь уходит со страницы и возвращается, ожидая сохранить фильтры, форму, scroll position и локальное UI-состояние.
KeepAlive легко использовать неправильно. Если кешировать все маршруты, растет память. Если данные никогда не обновляются, пользователь видит старую информацию. Если имена компонентов и route keys не контролируются, кеш может вести себя неожиданно.
Ссылки:
Когда KeepAlive полезен
Используйте KeepAlive для страниц, где сохранение локального состояния улучшает workflow:
- Списки с фильтрами, поиском, вкладками и scroll position.
- Многошаговые формы, где пользователь может уйти и вернуться.
- Dashboards с дорогим локальным rendering.
- Admin tabs, которые не должны сбрасываться.
Избегайте KeepAlive, если:
- Страницу дешево создать заново.
- Страница всегда должна показывать свежие данные.
- На странице есть sensitive data, которые должны исчезать при уходе.
- Компонент держит timers, subscriptions или большие objects, которые сложно поставить на паузу.
По умолчанию кеш должен быть выборочным, а не для всех маршрутов.
Базовая настройка route-level cache
Vue Router передает rendered route component через slot RouterView. Это удобное место для KeepAlive.
<!-- App.vue -->
<template>
<RouterView v-slot="{ Component, route }">
<KeepAlive :include="cachedComponentNames" :max="8">
<component
v-if="route.meta.keepAlive"
:is="Component"
:key="route.meta.cacheKey || route.name"
/>
</KeepAlive>
<component
v-if="!route.meta.keepAlive"
:is="Component"
:key="route.fullPath"
/>
</RouterView>
</template>
<script setup lang="ts">
const cachedComponentNames = ["UserList", "OrderList", "ReportDashboard"]
</script>
Route metadata держат решение рядом с определением route.
// router.ts
import type { RouteRecordRaw } from "vue-router"
export const routes: RouteRecordRaw[] = [
{
path: "/users",
name: "Users",
component: () => import("./views/UserList.vue"),
meta: {
keepAlive: true,
cacheKey: "users-list"
}
},
{
path: "/users/:id",
name: "UserDetail",
component: () => import("./views/UserDetail.vue"),
meta: {
keepAlive: false
}
}
]
Держите имена компонентов стабильными при использовании include или exclude, потому что Vue сопоставляет кеш по имени.
<!-- UserList.vue -->
<script setup lang="ts">
defineOptions({
name: "UserList"
})
</script>
Обновлять данные без сброса UI
Самая частая ошибка — устаревшие данные. Кешированная страница может хранить старый API result после возврата.
Используйте onActivated, чтобы решить, нужно ли обновление. Не перезагружайте все при каждой активации.
<script setup lang="ts">
import { onActivated, onDeactivated, ref } from "vue"
const users = ref<User[]>([])
const loading = ref(false)
const lastLoadedAt = ref(0)
const STALE_AFTER_MS = 60_000
async function loadUsers() {
loading.value = true
try {
users.value = await fetchUsers()
lastLoadedAt.value = Date.now()
} finally {
loading.value = false
}
}
onActivated(() => {
const isStale = Date.now() - lastLoadedAt.value > STALE_AFTER_MS
if (!users.value.length || isStale) {
void loadUsers()
}
})
onDeactivated(() => {
// Pause work that should not continue while the page is hidden.
})
</script>
Так сохраняется локальный UI-state, но старые данные обновляются.
Сохранение scroll position
KeepAlive сохраняет экземпляр компонента, но scroll может зависеть от layout, transitions или контейнера.
<template>
<main ref="scrollEl" class="page-scroll">
<!-- list content -->
</main>
</template>
<script setup lang="ts">
import { nextTick, onActivated, onDeactivated, ref } from "vue"
const scrollEl = ref<HTMLElement | null>(null)
let savedTop = 0
onDeactivated(() => {
savedTop = scrollEl.value?.scrollTop ?? 0
})
onActivated(async () => {
await nextTick()
if (scrollEl.value) {
scrollEl.value.scrollTop = savedTop
}
})
</script>
Проверьте direct navigation, back button и programmatic route changes.
Ограничить размер кеша
max ограничивает число instances, которые KeepAlive хранит. При превышении Vue удаляет старые cache entries.
<KeepAlive :max="8">
<component :is="Component" />
</KeepAlive>
Выбирайте лимит по реальным workflows. Не ставьте большое число, чтобы скрыть проблемы управления состоянием.
Избегать утечек памяти
Кешированные компоненты deactivated, но не unmounted. Поэтому часть cleanup должна быть в onDeactivated, финальная — в onUnmounted.
Пауза или удаление:
- Intervals и timeouts.
- WebSocket или SSE listeners, которые не должны работать в background.
- ResizeObserver и IntersectionObserver.
- Global event listeners.
- Большие временные arrays, maps или blobs.
import { onActivated, onDeactivated, onUnmounted } from "vue"
let timer: number | undefined
function startPolling() {
timer = window.setInterval(refreshStatus, 15_000)
}
function stopPolling() {
if (timer) {
window.clearInterval(timer)
timer = undefined
}
}
onActivated(startPolling)
onDeactivated(stopPolling)
onUnmounted(stopPolling)
Если память растет после повторной навигации, используйте browser devtools.
Cache keys и динамические маршруты
Динамическим маршрутам нужны аккуратные keys. Если detail pages используют одно имя и один key, они могут разделить cache instance неосознанно.
<!-- Share one cached instance for the user list -->
<component :is="Component" :key="route.name" />
<!-- Separate cache entry for each route parameter -->
<component :is="Component" :key="route.fullPath" />
<!-- Controlled custom key -->
<component :is="Component" :key="route.meta.cacheKey || route.fullPath" />
Не используйте fullPath бездумно для списков с множеством query combinations. Это может создать слишком много entries.
Практические правила
Используйте KeepAlive, когда у страницы есть ценный local state. Не используйте как общий performance switch.
Хорошие кандидаты:
- Search result lists.
- Form drafts.
- Dashboards с дорогим chart setup.
- Tabs, где ожидается instant switching.
Плохие кандидаты:
- Authentication screens.
- Payment или security-sensitive pages.
- Страницы, которые должны refetch каждый раз.
- Detail pages с большим числом unique IDs без clear cache limit.
Финальный checklist
Перед релизом проверьте:
- Только выбранные routes используют KeepAlive.
- Cached components имеют стабильные names.
- Dynamic routes имеют intentional keys.
maxустановлен разумно.- Data refresh использует
onActivated, когда нужно. - Timers, observers и listeners паузятся при deactivation.
- Sensitive pages не кешируются.
- Back button, direct navigation и tab switching протестированы.
KeepAlive надежен, когда используется для сохранения состояния, а не как автоматическая performance magic.
Попробуйте эти локальные браузерные инструменты — регистрация не требуется →