大家好,我是一名测试开发工程师,正在计划开源一款面向项目的自动化测试框架,该框架思想源于华为企业网,并且已成功实践于多个项目,包括华为商城、低代码平台等等,欢迎关注我。
一、keep-alive 的实现原理和缓存策略
- 获取 keep-alive 包裹着的第一个子组件对象及其组件名
- 根据设定的 include/exclude(如果有)进行条件匹配,决定是否缓存。否,则直接返回组件实例;是,则继续往下执行。
- 根据组件 ID 和 tag 生成缓存 Key,并在缓存对象中查找是否已缓存过该组件实例。如果存在,直接取出缓存值;否则,重新加载组件实例,并在缓存对象中存储该组件实例。
- 更新该 key 在 this.keys 中的位置(更新 key 的位置是实现LRU置换策略的关键)。
- 检查缓存的实例数量是否超过 max 的设置值,超过,则根据 LRU 置换策略删除最近最久未使用的实例(即是下标为 0 的那个 key)。
二、动态移除缓存的实现策略
- 通过2个参数控制:
- keepNumber:用于计数,将用来重命名组件实例的缓存 Key值。
- keepInclude:用于记录组件实例的缓存 Key值。
- 最终是通过改变组件关联的缓存 Key值,实现是否使用缓存,间接实现移除缓存的目标。
- 组件实例打开时,判断在keepInclude是否有记录,如果没有,则keepNumber+1,并进行赋值记录。
- 当需要移除缓存时(即下次打开不使用缓存),从keepInclude中移除该组件实例。
- 如果无限制的这样操作,会导致缓存越来越多。这时候就需要使用到 keep-alive自身的LRU置换策略。通过指定max值,确定keep-alive的缓存数量。
三、实例代码(vue3+TS+elementUiPlus)
<template>
<!--目录选择,用于操作打开tab页签,即打开组件实例-->
<el-menu :default-active="myMenuSec">
<template v-for="(name, path, index) in menuInfo" :key="index">
<el-menu-item :index="path" @click="my_tabs_add(path)">
<template #title>
<span>{{ name }}</span>
</template>
</el-menu-item>
</template>
</el-menu>
<!--tab页签-->
<el-tabs
v-model="myTabSec"
type="card"
closable
@tab-change="my_tabs_change"
@tab-remove="my_tabs_remove"
>
<el-tab-pane
v-for="(name,path) in myTabs"
:key="path"
:label="name"
:name="path"
/>
</el-tabs>
<!--展示组件实例,通过 v-if keepNumber>0 可以避免报错-->
<router-view v-if="keepNumber > 0" v-slot="{ Component }">
<keep-alive max="8">
<component
:key="keepInclude[router.currentRoute.value.path]"
:is="Component"
/>
</keep-alive>
</router-view>
</template>
<script lang="ts" setup>
import {ref} from "vue";
import router from "@/router";
const keepInclude = ref<any>({})
const keepNumber = ref(0)
const myMenuSec = ref("");
const menuInfo: any = {
"/testPage1": "测试页_1",
"/testPage2": "测试页_2",
"/testPage3": "测试页_3",
"/testPage4": "测试页_4",
}
const myTabLast = ref("");
const myTabSec = ref("");
const myTabs = ref<any>({});
const my_tabs_add = (path:string) => {
if (myTabSec.value != path) {
myTabs.value[path] = menuInfo[path];
myTabSec.value = path;
}
}
const my_tabs_change = async (path:string) => {
if (myTabLast.value != path){
if (!keepInclude.value[path]) {
// 记录组件实例的缓存Key值
keepNumber.value += 1;
keepInclude.value[path] = keepNumber.value
}
await router.replace({path: path});
myMenuSec.value = path;
myTabLast.value = path;
}
}
const my_tabs_remove = (path:string) => {
delete myTabs.value[path];
if (myTabSec.value == path) {
if (Object.keys(myTabs.value).length > 1) {
myTabSec.value = Object.keys(myTabs.value)[0];
} else {
myTabSec.value = ""
}
}
// 移除组件实例的记录
delete keepInclude.value[path];
}
</script>