Когда компонент убирается из DOM (например, при переключении вкладок), Vue по умолчанию его **уничтожает** — вызывается onUnmounted, всё состояние теряется. При возврате компонент создаётся заново с нуля.
<!-- БЕЗ KeepAlive — форма сбрасывается при переключении вкладок -->
<component :is="currentTab" /><KeepAlive> оборачивает динамические компоненты и кэширует их экземпляры:
<!-- С KeepAlive — состояние сохраняется -->
<KeepAlive>
<component :is="currentTab" />
</KeepAlive>Компонент больше не уничтожается — он просто «прячется». При возврате восстанавливается с прежним состоянием.
Кэшировать можно не все компоненты, а только выбранные:
<!-- По имени компонента (string) -->
<KeepAlive include="TabA,TabB">
<component :is="current" />
</KeepAlive>
<!-- Массив -->
<KeepAlive :include="['TabA', 'TabB']">
<component :is="current" />
</KeepAlive>
<!-- RegExp -->
<KeepAlive :include="/^Tab/">
<component :is="current" />
</KeepAlive>
<!-- exclude — кэшировать всё КРОМЕ -->
<KeepAlive exclude="HeavyEditor">
<component :is="current" />
</KeepAlive>Имя компонента определяется из опции name или имени файла при <script setup>.
<!-- Кэшировать максимум 5 компонентов (LRU-стратегия) -->
<KeepAlive :max="5">
<component :is="current" />
</KeepAlive>При превышении лимита самый давно используемый компонент вытесняется из кэша и уничтожается.
Кэшированные компоненты не получают обычные хуки mounted/unmounted при переключении. Вместо них используются:
import { onActivated, onDeactivated } from 'vue'
// Вызывается каждый раз, когда компонент становится видимым
onActivated(() => {
console.log('Компонент показан')
fetchLatestData() // Обновляем данные при возврате
startPolling() // Возобновляем опрос сервера
})
// Вызывается каждый раз, когда компонент скрывается
onDeactivated(() => {
console.log('Компонент скрыт')
stopPolling() // Останавливаем лишние запросы
saveScrollPosition() // Сохраняем позицию прокрутки
})При первом монтировании: onMounted → onActivated.
При скрытии: onDeactivated (без onUnmounted).
При уничтожении из кэша: onDeactivated → onUnmounted.
**Система вкладок** — самый распространённый случай:
<KeepAlive :include="cachedTabs" :max="10">
<component :is="activeTab" :key="activeTab.name" />
</KeepAlive>**Многошаговый wizard** — сохраняет данные заполненных шагов:
<KeepAlive>
<component :is="currentStep" />
</KeepAlive>Маршруты в Vue Router:
<RouterView v-slot="{ Component }">
<KeepAlive :include="cachedRoutes">
<component :is="Component" />
</KeepAlive>
</RouterView>LRU-кэш — именно такой алгоритм использует KeepAlive с опцией max для вытеснения компонентов
// KeepAlive с max использует алгоритм LRU (Least Recently Used).
// Реализуем его и посмотрим, как кэшируются "компоненты".
class LRUCache {
constructor(capacity) {
this.capacity = capacity
this.cache = new Map() // Map сохраняет порядок вставки
}
// Получить значение (обновляет "недавность" использования)
get(key) {
if (!this.cache.has(key)) return undefined
// Перемещаем в конец (самый "свежий")
const value = this.cache.get(key)
this.cache.delete(key)
this.cache.set(key, value)
return value
}
// Добавить значение
set(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key) // удаляем старую позицию
} else if (this.cache.size >= this.capacity) {
// Вытесняем наименее недавно используемый (первый в Map)
const lruKey = this.cache.keys().next().value
const lruVal = this.cache.get(lruKey)
console.log(`[LRU] Вытеснен: "${lruKey}" (onUnmounted)`)
if (lruVal.onUnmounted) lruVal.onUnmounted()
this.cache.delete(lruKey)
}
this.cache.set(key, value)
}
has(key) { return this.cache.has(key) }
keys() { return [...this.cache.keys()] }
}
// Симуляция KeepAlive с max
class KeepAlive {
constructor({ max = Infinity, include = null, exclude = null } = {}) {
this.lru = new LRUCache(max)
this.include = include // Set имён или null (все)
this.exclude = exclude // Set имён или null (никакие)
this.current = null
}
shouldCache(name) {
if (this.exclude && this.exclude.has(name)) return false
if (this.include && !this.include.has(name)) return false
return true
}
activate(name, factory) {
// Деактивируем текущий
if (this.current) {
console.log(`[KeepAlive] onDeactivated: "${this.current.name}"`)
if (this.current.onDeactivated) this.current.onDeactivated()
}
let instance
if (this.shouldCache(name) && this.lru.has(name)) {
// Восстанавливаем из кэша
instance = this.lru.get(name)
console.log(`[KeepAlive] Восстановлен из кэша: "${name}"`)
} else {
// Создаём новый
instance = factory()
instance.name = name
console.log(`[KeepAlive] Создан новый: "${name}" (onMounted)`)
if (instance.onMounted) instance.onMounted()
}
// Кэшируем если нужно
if (this.shouldCache(name)) {
this.lru.set(name, instance)
}
console.log(`[KeepAlive] onActivated: "${name}"`)
if (instance.onActivated) instance.onActivated()
this.current = instance
console.log('Кэш:', this.lru.keys())
return instance
}
}
// --- "Компоненты" ---
function makeTab(name) {
let visitCount = 0
return {
name,
visitCount,
onMounted() { console.log(` [${name}] onMounted`) },
onActivated() { visitCount++; console.log(` [${name}] onActivated (посещений: ${visitCount})`) },
onDeactivated(){ console.log(` [${name}] onDeactivated`) },
onUnmounted() { console.log(` [${name}] onUnmounted (кэш вытеснен!)`) },
}
}
// max=3 — кэшируем не более 3 компонентов
const keepAlive = new KeepAlive({ max: 3 })
console.log('--- Первые открытия ---')
keepAlive.activate('TabA', () => makeTab('TabA'))
keepAlive.activate('TabB', () => makeTab('TabB'))
keepAlive.activate('TabA', () => makeTab('TabA')) // из кэша
keepAlive.activate('TabC', () => makeTab('TabC'))
console.log('\n--- Превышение max=3 ---')
keepAlive.activate('TabD', () => makeTab('TabD')) // вытесняет TabB (LRU)
keepAlive.activate('TabB', () => makeTab('TabB')) // пересоздаётся (был вытеснен)
Когда компонент убирается из DOM (например, при переключении вкладок), Vue по умолчанию его **уничтожает** — вызывается onUnmounted, всё состояние теряется. При возврате компонент создаётся заново с нуля.
<!-- БЕЗ KeepAlive — форма сбрасывается при переключении вкладок -->
<component :is="currentTab" /><KeepAlive> оборачивает динамические компоненты и кэширует их экземпляры:
<!-- С KeepAlive — состояние сохраняется -->
<KeepAlive>
<component :is="currentTab" />
</KeepAlive>Компонент больше не уничтожается — он просто «прячется». При возврате восстанавливается с прежним состоянием.
Кэшировать можно не все компоненты, а только выбранные:
<!-- По имени компонента (string) -->
<KeepAlive include="TabA,TabB">
<component :is="current" />
</KeepAlive>
<!-- Массив -->
<KeepAlive :include="['TabA', 'TabB']">
<component :is="current" />
</KeepAlive>
<!-- RegExp -->
<KeepAlive :include="/^Tab/">
<component :is="current" />
</KeepAlive>
<!-- exclude — кэшировать всё КРОМЕ -->
<KeepAlive exclude="HeavyEditor">
<component :is="current" />
</KeepAlive>Имя компонента определяется из опции name или имени файла при <script setup>.
<!-- Кэшировать максимум 5 компонентов (LRU-стратегия) -->
<KeepAlive :max="5">
<component :is="current" />
</KeepAlive>При превышении лимита самый давно используемый компонент вытесняется из кэша и уничтожается.
Кэшированные компоненты не получают обычные хуки mounted/unmounted при переключении. Вместо них используются:
import { onActivated, onDeactivated } from 'vue'
// Вызывается каждый раз, когда компонент становится видимым
onActivated(() => {
console.log('Компонент показан')
fetchLatestData() // Обновляем данные при возврате
startPolling() // Возобновляем опрос сервера
})
// Вызывается каждый раз, когда компонент скрывается
onDeactivated(() => {
console.log('Компонент скрыт')
stopPolling() // Останавливаем лишние запросы
saveScrollPosition() // Сохраняем позицию прокрутки
})При первом монтировании: onMounted → onActivated.
При скрытии: onDeactivated (без onUnmounted).
При уничтожении из кэша: onDeactivated → onUnmounted.
**Система вкладок** — самый распространённый случай:
<KeepAlive :include="cachedTabs" :max="10">
<component :is="activeTab" :key="activeTab.name" />
</KeepAlive>**Многошаговый wizard** — сохраняет данные заполненных шагов:
<KeepAlive>
<component :is="currentStep" />
</KeepAlive>Маршруты в Vue Router:
<RouterView v-slot="{ Component }">
<KeepAlive :include="cachedRoutes">
<component :is="Component" />
</KeepAlive>
</RouterView>LRU-кэш — именно такой алгоритм использует KeepAlive с опцией max для вытеснения компонентов
// KeepAlive с max использует алгоритм LRU (Least Recently Used).
// Реализуем его и посмотрим, как кэшируются "компоненты".
class LRUCache {
constructor(capacity) {
this.capacity = capacity
this.cache = new Map() // Map сохраняет порядок вставки
}
// Получить значение (обновляет "недавность" использования)
get(key) {
if (!this.cache.has(key)) return undefined
// Перемещаем в конец (самый "свежий")
const value = this.cache.get(key)
this.cache.delete(key)
this.cache.set(key, value)
return value
}
// Добавить значение
set(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key) // удаляем старую позицию
} else if (this.cache.size >= this.capacity) {
// Вытесняем наименее недавно используемый (первый в Map)
const lruKey = this.cache.keys().next().value
const lruVal = this.cache.get(lruKey)
console.log(`[LRU] Вытеснен: "${lruKey}" (onUnmounted)`)
if (lruVal.onUnmounted) lruVal.onUnmounted()
this.cache.delete(lruKey)
}
this.cache.set(key, value)
}
has(key) { return this.cache.has(key) }
keys() { return [...this.cache.keys()] }
}
// Симуляция KeepAlive с max
class KeepAlive {
constructor({ max = Infinity, include = null, exclude = null } = {}) {
this.lru = new LRUCache(max)
this.include = include // Set имён или null (все)
this.exclude = exclude // Set имён или null (никакие)
this.current = null
}
shouldCache(name) {
if (this.exclude && this.exclude.has(name)) return false
if (this.include && !this.include.has(name)) return false
return true
}
activate(name, factory) {
// Деактивируем текущий
if (this.current) {
console.log(`[KeepAlive] onDeactivated: "${this.current.name}"`)
if (this.current.onDeactivated) this.current.onDeactivated()
}
let instance
if (this.shouldCache(name) && this.lru.has(name)) {
// Восстанавливаем из кэша
instance = this.lru.get(name)
console.log(`[KeepAlive] Восстановлен из кэша: "${name}"`)
} else {
// Создаём новый
instance = factory()
instance.name = name
console.log(`[KeepAlive] Создан новый: "${name}" (onMounted)`)
if (instance.onMounted) instance.onMounted()
}
// Кэшируем если нужно
if (this.shouldCache(name)) {
this.lru.set(name, instance)
}
console.log(`[KeepAlive] onActivated: "${name}"`)
if (instance.onActivated) instance.onActivated()
this.current = instance
console.log('Кэш:', this.lru.keys())
return instance
}
}
// --- "Компоненты" ---
function makeTab(name) {
let visitCount = 0
return {
name,
visitCount,
onMounted() { console.log(` [${name}] onMounted`) },
onActivated() { visitCount++; console.log(` [${name}] onActivated (посещений: ${visitCount})`) },
onDeactivated(){ console.log(` [${name}] onDeactivated`) },
onUnmounted() { console.log(` [${name}] onUnmounted (кэш вытеснен!)`) },
}
}
// max=3 — кэшируем не более 3 компонентов
const keepAlive = new KeepAlive({ max: 3 })
console.log('--- Первые открытия ---')
keepAlive.activate('TabA', () => makeTab('TabA'))
keepAlive.activate('TabB', () => makeTab('TabB'))
keepAlive.activate('TabA', () => makeTab('TabA')) // из кэша
keepAlive.activate('TabC', () => makeTab('TabC'))
console.log('\n--- Превышение max=3 ---')
keepAlive.activate('TabD', () => makeTab('TabD')) // вытесняет TabB (LRU)
keepAlive.activate('TabB', () => makeTab('TabB')) // пересоздаётся (был вытеснен)
Реализуй класс `ComponentCache` — кэш компонентов с LRU-вытеснением. Конструктор принимает `capacity` (максимум кэшируемых компонентов). Метод `add(name, instance)` добавляет компонент в кэш; если превышен capacity — вытесняет наименее недавно используемый и вызывает у него `destroy()` если метод существует. Метод `get(name)` возвращает компонент из кэша (или undefined) и помечает его как недавно использованный. Метод `has(name)` проверяет наличие в кэше. Метод `size()` возвращает текущий размер кэша.
Map в JavaScript сохраняет порядок вставки ключей. Самый старый (LRU) ключ — первый: this.cache.keys().next().value. Чтобы "обновить" позицию ключа: const val = this.cache.get(key); this.cache.delete(key); this.cache.set(key, val) — теперь он в конце.
Токены для AI-помощника закончились
Купи токены чтобы задавать вопросы AI прямо в уроке