Keepalive у Vue.js
<KeepAlive> - це вбудований компонент Vue, який кешує деактивовані дочірні компоненти в пам'яті замість їх знищення, зберігаючи стан і DOM між перемиканнями.
Теорія
Коротко
- Як поставити гру на паузу замість виходу: позиція персонажа залишається там, де ти залишив.
v-ifзнищує і ремонтує компонент (стан губиться);<KeepAlive>деактивує і реактивує (стан залишається).- Використовуй, якщо користувач перемикає вигляди більше двох разів за сесію і стан важливий: форми, скрол, списки.
- Не використовуй для одноразових сторінок або компонентів, які займають багато пам'яті.
- Два додаткових хуки:
onActivatedіonDeactivated.
Швидкий приклад
<template>
<button @click="tab = 'home'">Home</button>
<button @click="tab = 'form'">Form</button>
<!-- Без KeepAlive: перемикаєш вкладку — форма скидається -->
<!-- <Form v-if="tab === 'form'" /> -->
<!-- З KeepAlive: ввів "hello", перейшов, повернувся — "hello" на місці -->
<KeepAlive>
<Home v-if="tab === 'home'" />
<Form v-else />
</KeepAlive>
</template>
<script setup>
import { ref } from 'vue'
const tab = ref('home')
</script>Введи щось у форму, перейди на Home, поверни назад. Дані на місці. Ось і вся суть.
Головна різниця від v-if
Без <KeepAlive> перемикання через v-if запускає повний цикл знищення: спрацьовує beforeUnmount, DOM очищається, весь реактивний стан і вотчери зникають. При повторному монтуванні все починається з нуля. З <KeepAlive> Vue переміщує компонент у прихований контейнер поза активним деревом DOM. Дерево vnode, реактивний стан і слухачі подій залишаються цілими. При реактивації Vue повертає той самий vnode і викликає onActivated.
Коли використовувати
- Вкладки з полями введення → використовуй
<KeepAlive>(скрол і дані форми зберігаються). - Багатокрокові форми з кнопкою "назад" → використовуй (прогрес не губиться).
- Навігація «Список → Деталі → Назад» → використовуй (список залишається прокрученим там, де треба).
- Важкі компоненти, які відвідують один раз → пропускай (кеш росте і займає пам'ять).
- Статичні сторінки, що завжди завантажують свіжі дані → пропускай (кеш тут лише накладні витрати).
Контроль кешу: include, exclude і max
За замовчуванням <KeepAlive> кешує все, що в нього загорнуто. У великих застосунках це стає проблемою. Три пропси дають контроль:
<!-- Кешувати лише ProductList і ShoppingCart -->
<KeepAlive include="ProductList,ShoppingCart">
<component :is="currentView" />
</KeepAlive>
<!-- Кешувати все, крім Settings -->
<KeepAlive exclude="Settings">
<component :is="currentView" />
</KeepAlive>
<!-- LRU: не більше 5 екземплярів; найстаріший видаляється при перевищенні -->
<KeepAlive :max="5">
<component :is="currentView" />
</KeepAlive>include і exclude перевіряють опцію name компонента, а не назву файлу чи HTML-тег. Якщо у компонента немає name, фільтр його ігнорує і він поводиться як звичайний v-if. Завжди встановлюй name для компонентів, які плануєш вибірково кешувати.
Хуки життєвого циклу
<KeepAlive> додає два хуки, які замінюють звичайну пару mount/unmount для кешованих компонентів:
<script setup>
import { onActivated, onDeactivated } from 'vue'
// Спрацьовує, коли компонент повертається в DOM з кешу
onActivated(() => {
fetchLatestData() // оновити застарілі дані
})
// Спрацьовує, коли компонент покидає DOM, але залишається в кеші
onDeactivated(() => {
clearInterval(timer) // зупинити фонову роботу
})
</script>onDeactivated - це не beforeUnmount. Компонент живий, він просто пішов з екрана. onActivated - це не onMounted: він спрацьовує щоразу, коли компонент повертається, а не тільки при першому рендері. При першому рендері спрацьовують обидва: і onMounted, і onActivated.
Як це працює всередині
Vue 3 зберігає Map кешованих vnode з ключем за іменем компонента або uid. Коли компонент деактивується, рендерер переміщує його в прихований контейнер поза живим деревом DOM. Нічого не знищується. Коли користувач повертається, Vue витягує vnode з Map, вставляє назад і патчить лише те, що змінилось. LRU-виселення через :max відстежує порядок активацій і видаляє найстаріший запис при перевищенні ліміту.
З Vue Router
Кешування на рівні маршрутів - типова практика в SPA. Стандартний патерн виглядає так:
<!-- App.vue -->
<template>
<RouterView v-slot="{ Component }">
<KeepAlive include="Home,Dashboard">
<component :is="Component" :key="$route.path" />
</KeepAlive>
</RouterView>
</template>:key="$route.path" дає різним шляхам окремі записи в кеші навіть для одного й того самого компонента. Корисно для сторінки профілю: /users/1 і /users/2 матимуть незалежний стан.
Типові помилки
1. Немає name у компонента.
include="UserForm" перевіряє опцію name, а не назву файлу. Компонент без name не буде вибірково кешуватись.
<!-- Неправильно: include не має з чим звіритись -->
<KeepAlive include="UserForm">
<component :is="currentView" /> <!-- UserForm.vue без опції name -->
</KeepAlive>
<!-- Правильно: встанови name через defineOptions -->
<script setup>
defineOptions({ name: 'UserForm' })
</script>2. Немає :max у застосунку з динамічними вкладками.
Без ліміту кожен кешований компонент залишається в пам'яті. У продакшн-застосунках з панелями керування кеш може непомітно зайняти сотні мегабайт. На мобільних пристроях 20+ кешованих вигляди дають відчутне сповільнення.
<!-- Неправильно: необмежений кеш -->
<KeepAlive>
<component :is="currentView" />
</KeepAlive>
<!-- Правильно -->
<KeepAlive :max="10">
<component :is="currentView" />
</KeepAlive>3. :key скидає кеш, навіть коли цього не очікуєш.
Якщо додати :key до кешованого компонента, Vue вважає кожен унікальний ключ окремим екземпляром. Зміна ключа створює новий запис у кеші, а старий стан зникає навіть з <KeepAlive>. Використовуй :key усвідомлено.
4. onDeactivated плутають з beforeUnmount.
Прибирання в onDeactivated має лише ставити речі на паузу (таймери, підписки). Якщо там вручну очистити реактивний стан, компонент зламається при реактивації.
5. include з HTML-тегами нічого не дає.
<KeepAlive include="div"> не кешує нічого. Порівняння йде тільки з іменами компонентів.
Де використовується
- Vue Router в адмін-панелях: кешують
HomeіDashboard, щоб фільтри і стан таблиць зберігались між переходами. - Element Plus і Vant UI: панелі вкладок загортають вміст у
<KeepAlive>для збереження стану форм. - Nuxt 3: кешування на рівні сторінок через мета-поле
keepaliveмаршруту для сторінок каталогу. - Pinia devtools: кешує панелі інспектора сховища, щоб стан фільтрів зберігався між перемиканнями.
Питання на співбесіді
Q: Яка різниця між onDeactivated і beforeUnmount?
A: onDeactivated спрацьовує, коли компонент покидає DOM, але залишається живим у кеші. beforeUnmount спрацьовує перед повним знищенням компонента. З <KeepAlive> хук beforeUnmount при перемиканні не спрацьовує — лише коли компонент виселяється з кешу або батьківський компонент знищується.
Q: Як працює LRU-виселення з :max?
A: Vue відстежує порядок активацій у списку. Коли новий компонент перевищить :max, найменш нещодавно активований запис виселяється і повністю знищується. Це стандартна LRU-логіка.
Q: Чому onActivated спрацьовує і при першому монтуванні?
A: Vue викликає і onMounted, і onActivated при першому рендері. Якщо потрібна логіка тільки при першому монтуванні, поклади її в onMounted. onActivated призначений для логіки, що має виконуватись при кожному поверненні компонента в DOM.
Q: Як кешувати компонент окремо для різних параметрів маршруту, наприклад /users/1 і /users/2?
A: Використовуй :key="$route.fullPath" або :key="$route.params.id" на кешованому компоненті. Кожен унікальний ключ створює окремий запис у кеші, тому обидва профілі матимуть незалежний стан.
Q: Чи впливає <KeepAlive> на рендеринг на сервері (SSR)?
A: Ні. <KeepAlive> працює лише на клієнті. Під час SSR він не робить нічого і вмикається лише після гідрації в браузері.
Приклади
Базова навігація по вкладках з контролем кешу
<template>
<div>
<button @click="activeTab = 'products'">Товари</button>
<button @click="activeTab = 'cart'">Кошик</button>
<KeepAlive include="ProductList,ShoppingCart" :max="5">
<ProductList v-if="activeTab === 'products'" />
<ShoppingCart v-else />
</KeepAlive>
</div>
</template>
<script setup>
import { ref } from 'vue'
import ProductList from './ProductList.vue'
import ShoppingCart from './ShoppingCart.vue'
// Обидва компоненти мають defineOptions({ name: '...' })
const activeTab = ref('products')
</script>Додай товари до кошика, перейди на «Товари», повернись назад. Кількості на місці. Це патерн, що використовується в більшості SPA e-commerce з навігацією по вкладках.
Оновлення даних при кожній реактивації
<script setup>
import { ref, onActivated } from 'vue'
const orders = ref([])
async function loadOrders() {
orders.value = await fetch('/api/orders').then(r => r.json())
}
// Спрацьовує при першому монтуванні І при кожному поверненні на вкладку
onActivated(() => {
loadOrders()
})
</script>Компонент зберігає DOM і позицію скролу з кешу, але при кожній активації завантажує свіжі дані. Користувач бачить список миттєво (з кешу), а потім отримує оновлений вміст після завершення запиту.
Кешування на рівні маршрутів з ключами per-route
<!-- App.vue -->
<template>
<RouterView v-slot="{ Component }">
<KeepAlive :max="10">
<component
:is="Component"
:key="$route.fullPath"
/>
</KeepAlive>
</RouterView>
</template>:key="$route.fullPath" дає /dashboard/sales і /dashboard/analytics окремі слоти в кеші, навіть якщо вони використовують один компонент. Без ключа обидва маршрути ділили б один кешований екземпляр і один стан — що майже ніколи не потрібно.
Коротка відповідь
Для співбесідиКоротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.