一、keep-alive基本概念
keep-alive是Vue的內置組件,用于緩存組件實例,避免重復渲染。它具有以下特點:
- 抽象組件:自身不會渲染DOM,也不會出現在父組件鏈中
- 包裹動態組件:緩存不活動的組件實例,而不是銷毀它們
- 生命周期:提供activated和deactivated鉤子函數
二、keep-alive核心實現原理
1. 基本工作流程
- 判斷當前組件是否需要緩存
- 生成組件唯一key
- 緩存組件實例
- 在被包裹組件上觸發對應的生命周期鉤子
2. 緩存策略
采用LRU(Least Recently Used)算法:
- 設置最大緩存數量(max屬性)
- 優先刪除最久未使用的組件
- 新組件加入時,若達到上限則刪除最舊組件
三、Vue2實現原理
// Vue2 中 keep-alive 的核心實現
export default {name: 'keep-alive',abstract: true, // 抽象組件標識props: {include: [String, RegExp, Array],exclude: [String, RegExp, Array],max: [String, Number]},created () {this.cache = Object.create(null) // 緩存對象this.keys = [] // 緩存key數組},destroyed () {// 銷毀所有緩存實例for (const key in this.cache) {pruneCacheEntry(this.cache, key)}},mounted () {// 監聽include和exclude的變化this.$watch('include', val => {pruneCache(this, name => matches(val, name))})this.$watch('exclude', val => {pruneCache(this, name => !matches(val, name))})},render () {const slot = this.$slots.defaultconst vnode = getFirstComponentChild(slot)const componentOptions = vnode && vnode.componentOptionsif (componentOptions) {const name = getComponentName(componentOptions)const { include, exclude } = this// 判斷是否需要緩存if ((include && (!name || !matches(include, name))) ||(exclude && name && matches(exclude, name))) {return vnode}const { cache, keys } = thisconst key = vnode.key == null? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : ''): vnode.key// 命中緩存if (cache[key]) {vnode.componentInstance = cache[key].componentInstanceremove(keys, key)keys.push(key) // 調整key順序} else {cache[key] = vnode // 緩存組件keys.push(key)// 超過max限制時清理最久未使用的組件if (this.max && keys.length > parseInt(this.max)) {pruneCacheEntry(cache, keys[0])keys.shift()}}vnode.data.keepAlive = true}return vnode || (slot && slot[0])}
}
四、Vue3實現原理
// Vue3 中 keep-alive 的核心實現
export const KeepAliveImpl = {name: 'KeepAlive',__isKeepAlive: true,props: {include: [String, RegExp, Array],exclude: [String, RegExp, Array],max: [String, Number]},setup(props, { slots }) {const cache = new Map() // 使用Map存儲緩存const keys = new Set() // 使用Set存儲keysconst current = getCurrentInstance()// 緩存子樹const cacheSubtree = () => {if (current.subTree) {cache.set(current.subTree.key, current.subTree)keys.add(current.subTree.key)}}// 修剪緩存const pruneCache = (filter?: (name: string) => boolean) => {cache.forEach((vnode, key) => {const name = vnode.type.nameif (name && (!filter || filter(name))) {pruneCacheEntry(key)}})}// 清理緩存條目const pruneCacheEntry = (key: CacheKey) => {const cached = cache.get(key)if (!current || !isSameVNodeType(cached, current)) {unmount(cached)}cache.delete(key)keys.delete(key)}// 監聽include/exclude變化watch(() => [props.include, props.exclude],([include, exclude]) => {include && pruneCache(name => matches(include, name))exclude && pruneCache(name => !matches(exclude, name))})// 卸載時清理所有緩存onBeforeUnmount(() => {cache.forEach(cached => {unmount(cached)})})return () => {const children = slots.default?.()if (!children) return nullconst vnode = children[0]if (!vnode) return nullconst comp = vnode.typeconst name = comp.name// 檢查是否應該緩存if ((props.include && (!name || !matches(props.include, name))) ||(props.exclude && name && matches(props.exclude, name))) {return vnode}const key = vnode.key == null ? comp : vnode.keyconst cached = cache.get(key)// 命中緩存if (cached) {vnode.el = cached.elvnode.component = cached.component// 標記為kept-alivevnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE} else {// 緩存新組件cache.set(key, vnode)keys.add(key)// 超過max限制時清理if (props.max && cache.size > parseInt(props.max)) {pruneCacheEntry(keys.values().next().value)}}// 標記keepAlivevnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVEreturn vnode}}
}
五、Vue2和Vue3實現差異對比
1. 數據結構
- Vue2: 使用普通對象和數組存儲緩存
this.cache = Object.create(null) this.keys = []
- Vue3: 使用Map和Set存儲緩存
const cache = new Map() const keys = new Set()
2. 組件實現方式
- Vue2: 選項式API,通過created、mounted等生命周期實現
- Vue3: 組合式API,使用setup函數實現,邏輯更集中
3. 渲染機制
- Vue2: 在render函數中直接操作VNode
- Vue3: 使用新的渲染器架構,更好地支持Fragment和異步組件
4. 性能優化
- Vue3優勢:
- 更高效的響應式系統
- 更智能的編譯優化
- 更好的Tree-shaking支持
- 更完善的TypeScript支持
5. 生命周期鉤子
- Vue2:
export default {activated() {},deactivated() {} }
- Vue3:
import { onActivated, onDeactivated } from 'vue'setup() {onActivated(() => {})onDeactivated(() => {}) }
六、使用方法案例
1. Vue2中的使用方法
基礎用法
<!-- App.vue -->
<template><div id="app"><keep-alive><component :is="currentComponent"></component></keep-alive></div>
</template><script>
import ComponentA from './components/ComponentA.vue'
import ComponentB from './components/ComponentB.vue'export default {name: 'App',components: {ComponentA,ComponentB},data() {return {currentComponent: 'ComponentA'}}
}
</script>
配合路由使用
// router.js
import Vue from 'vue'
import VueRouter from 'vue-router'Vue.use(VueRouter)const routes = [{path: '/list',component: () => import('./views/List.vue'),meta: {keepAlive: true // 需要緩存的路由}},{path: '/detail',component: () => import('./views/Detail.vue'),meta: {keepAlive: false // 不需要緩存的路由}}
]export default new VueRouter({routes
})
<!-- App.vue -->
<template><div id="app"><!-- 緩存路由組件 --><keep-alive><router-view v-if="$route.meta.keepAlive"></router-view></keep-alive><!-- 不緩存的路由組件 --><router-view v-if="!$route.meta.keepAlive"></router-view></div>
</template>
使用include和exclude
<template><div id="app"><keep-alive :include="['ComponentA', 'ComponentB']" :exclude="['ComponentC']"><router-view></router-view></keep-alive></div>
</template><script>
export default {name: 'App'
}
</script>
2. Vue3中的使用方法
基礎用法
<!-- App.vue -->
<template><div id="app"><KeepAlive><component :is="currentComponent"></component></KeepAlive></div>
</template><script setup>
import { ref } from 'vue'
import ComponentA from './components/ComponentA.vue'
import ComponentB from './components/ComponentB.vue'const currentComponent = ref('ComponentA')
</script>
配合路由使用
// router.ts
import { createRouter, createWebHistory } from 'vue-router'const routes = [{path: '/list',component: () => import('./views/List.vue'),meta: {keepAlive: true}},{path: '/detail',component: () => import('./views/Detail.vue'),meta: {keepAlive: false}}
]export default createRouter({history: createWebHistory(),routes
})
<!-- App.vue -->
<template><div id="app"><RouterView v-slot="{ Component }"><KeepAlive><component :is="Component" v-if="$route.meta.keepAlive" /></KeepAlive><component :is="Component" v-if="!$route.meta.keepAlive" /></RouterView></div>
</template><script setup>
import { RouterView } from 'vue-router'
</script>
使用include和exclude
<!-- App.vue -->
<template><div id="app"><RouterView v-slot="{ Component }"><KeepAlive :include="['ListPage']" :max="10"><component :is="Component" /></KeepAlive></RouterView></div>
</template><script setup>
import { RouterView } from 'vue-router'
</script>
在組件中使用生命周期鉤子
<!-- ListPage.vue -->
<template><div class="list-page"><ul><li v-for="item in list" :key="item.id">{{ item.name }}</li></ul></div>
</template><script setup>
import { ref, onActivated, onDeactivated } from 'vue'const list = ref([])// 在組件被激活時觸發
onActivated(() => {console.log('組件被激活')// 可以在這里恢復組件的狀態,如滾動位置
})// 在組件被停用時觸發
onDeactivated(() => {console.log('組件被停用')// 可以在這里保存組件的狀態
})
</script>
七、總結
-
核心原理相同:
- 都使用LRU緩存策略
- 都支持include/exclude過濾
- 都實現了組件緩存和重用
-
主要改進:
- Vue3使用更現代的數據結構
- 更清晰的代碼組織方式
- 更好的性能優化
- 更強大的TypeScript支持
- 更完善的錯誤處理機制