Запропонувати правкуПокращити цю статтюДопрацюйте відповідь до «Keepalive у Vue.js». Ваші зміни проходять модерацію перед публікацією.Потрібне підтвердженняКонтентЩо ви змінюєте🇺🇸EN🇺🇦UAПереглядЗаголовок (UA)Коротка відповідь (UA)**`<KeepAlive>`** у Vue.js кешує екземпляри компонентів замість їх знищення при перемиканні, зберігаючи стан між переходами. ```vue <KeepAlive> <component :is="currentTab" /> </KeepAlive> ``` **Головне:** `v-if` знищує і ремонтує компонент (стан губиться); `<KeepAlive>` деактивує і реактивує (стан залишається). Контролюй кешування через `include`, `exclude` і `:max`.Показується над повною відповіддю для швидкого нагадування.Відповідь (UA)Зображення**`<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` окремі слоти в кеші, навіть якщо вони використовують один компонент. Без ключа обидва маршрути ділили б один кешований екземпляр і один стан — що майже ніколи не потрібно.Для рев’юераПримітка для модератора (необов’язково)Бачить лише модератор. Прискорює рев’ю.