Skip to main content

Keepalive у Vue.js

<KeepAlive> - це вбудований компонент Vue, який кешує деактивовані дочірні компоненти в пам'яті замість їх знищення, зберігаючи стан і DOM між перемиканнями.

Теорія

Коротко

  • Як поставити гру на паузу замість виходу: позиція персонажа залишається там, де ти залишив.
  • v-if знищує і ремонтує компонент (стан губиться); <KeepAlive> деактивує і реактивує (стан залишається).
  • Використовуй, якщо користувач перемикає вигляди більше двох разів за сесію і стан важливий: форми, скрол, списки.
  • Не використовуй для одноразових сторінок або компонентів, які займають багато пам'яті.
  • Два додаткових хуки: onActivated і onDeactivated.

Швидкий приклад

vue
<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> кешує все, що в нього загорнуто. У великих застосунках це стає проблемою. Три пропси дають контроль:

vue
<!-- Кешувати лише 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 для кешованих компонентів:

vue
<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. Стандартний патерн виглядає так:

vue
<!-- 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 не буде вибірково кешуватись.

vue
<!-- Неправильно: include не має з чим звіритись --> <KeepAlive include="UserForm"> <component :is="currentView" /> <!-- UserForm.vue без опції name --> </KeepAlive> <!-- Правильно: встанови name через defineOptions --> <script setup> defineOptions({ name: 'UserForm' }) </script>

2. Немає :max у застосунку з динамічними вкладками. Без ліміту кожен кешований компонент залишається в пам'яті. У продакшн-застосунках з панелями керування кеш може непомітно зайняти сотні мегабайт. На мобільних пристроях 20+ кешованих вигляди дають відчутне сповільнення.

vue
<!-- Неправильно: необмежений кеш --> <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 він не робить нічого і вмикається лише після гідрації в браузері.

Приклади

Базова навігація по вкладках з контролем кешу

vue
<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 з навігацією по вкладках.

Оновлення даних при кожній реактивації

vue
<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

vue
<!-- 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 окремі слоти в кеші, навіть якщо вони використовують один компонент. Без ключа обидва маршрути ділили б один кешований екземпляр і один стан — що майже ніколи не потрібно.

Коротка відповідь

Для співбесіди
Premium

Коротка відповідь допоможе вам впевнено відповідати на цю тему під час співбесіди.

Дочитали статтю?