vue3手動(dòng)刪除keepAlive緩存的方法
keepAlive的最大緩存數(shù)是無(wú)限大
當(dāng)我們未設(shè)置keepAlive的最大緩存數(shù)時(shí),當(dāng)緩存組件太多,會(huì)導(dǎo)致內(nèi)存溢出。
keepAlive最大緩存數(shù)測(cè)試實(shí)踐
- 如下腳本,給組件設(shè)置keepAlive緩存,并將組件的key設(shè)置為
$route.path
<router-view v-slot="{ Component }">
<keep-alive >
<component :is="Component" :key="$route.path"/>
</keep-alive>
</router-view>
- 如下腳本,設(shè)置一個(gè)動(dòng)態(tài)路由,不同id共用同一個(gè)組件
{
path: '/el/cascader/:id',
name: 'ElCascaderDemo2',
component: () => import('../views/ElCascaderDemo.vue')
},
- 如下腳本, 點(diǎn)擊每一行,可以不斷的新增緩存組件
<script setup lang="ts">
import { useRouter } from 'vue-router'
const router = useRouter()
const go = (id: number) => {
router.push({
path: '/el/cascader/' + id
})
}
</script>
<template>
<main>
<div v-for="i in 10000" @click="go(i)" class="item">
{{ i }}
</div>
</main>
</template>
- 下面腳本是keepAlive組件中的源碼,在內(nèi)部打斷點(diǎn),查看當(dāng)前cache中緩存的組件數(shù)是多少。
const cacheSubtree = () => {
if (pendingCacheKey != null) {
cache.set(pendingCacheKey, getInnerChild(instance.subTree));
}
};
onMounted(cacheSubtree);
onUpdated(cacheSubtree);
- 如下圖,當(dāng)cache中緩存12個(gè)組件時(shí),內(nèi)存已經(jīng)達(dá)到了2G,

如下圖,在繼續(xù)加壓后,內(nèi)存達(dá)到4019MB時(shí),頁(yè)面崩潰。

故在使用keepAlive緩存組件,一定要設(shè)置它的最大緩存數(shù)。
設(shè)置keepAlive最大緩存數(shù)
<router-view v-slot="{ Component }">
<keep-alive :max="10">
<component :is="Component" :key="$route.path"/>
</keep-alive>
</router-view>
設(shè)置最大緩存數(shù)等于10以后,keepAlive組件內(nèi)緩存變量cache的size<=9,內(nèi)存位置在1900MB。
實(shí)現(xiàn)手動(dòng)刪除緩存組件
實(shí)現(xiàn)手動(dòng)刪除緩存組件的方式是:動(dòng)態(tài)增刪keepAlive組件的exclude屬性。exclude屬性的值可以是一個(gè)組件名稱組成的數(shù)組。在 3.2.34 或以上的版本中,使用 <script setup> 的單文件組件會(huì)自動(dòng)根據(jù)文件名生成對(duì)應(yīng)的 name 選項(xiàng),無(wú)需再手動(dòng)聲明。但目前組件的key等于route.path,/el/cascader/1和/el/cascader/2會(huì)緩存兩份,手動(dòng)刪除這類組件,需要給它們各自一個(gè)名稱,而不是都用它們指向的那一個(gè)組件的名稱。
第一步:封裝動(dòng)態(tài)組件component的is屬性的賦值,使用route.path作為組件的名稱。
<router-view v-slot="{ Component }">
<keep-alive :max="10">
<component :is="formatComponentInstance(Component, $route?.path)" :key="$route.path"/>
</keep-alive>
</router-view>
let wrapperMap = new Map()
const formatComponentInstance = (component : Component, path:string ) => {
let wrapper
if (wrapperMap.has(path)) {
wrapper = wrapperMap.get(path)
} else {
wrapper = {
name: path,
render(){
return h(component) // h的第一個(gè)參數(shù)可以是字符串,也可以是一個(gè)組件定義;h返回的是一個(gè)虛擬dom
}
}
wrapperMap.set(path, wrapper)
}
return h(wrapper)
}
第二步:實(shí)現(xiàn)一個(gè)簡(jiǎn)單內(nèi)頁(yè)簽,內(nèi)頁(yè)簽關(guān)閉時(shí)清除組件緩存
<template>
<el-tag
v-for="item in editableTabs"
:key="item.key"
closable
:disable-transitions="false"
@click="tabChange(item.key)"
@close="handleTabsEdit(item.key, 'remove', undefined)"
:type="editableKey === item.key ? 'primary':'info'"
>
{{ item.title }}
</el-tag>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue'
import type { Ref } from 'vue'
import { ElTag} from 'element-plus'
import { useRoute, useRouter} from 'vue-router'
const route = useRoute()
const router = useRouter()
import { useKeepAlive } from '../stores/index'
let tabIndex = 1
const editableKey = ref('')
const editableTabs : Ref<Array<{[key: string]: any}>> = ref([])
const visitedRoute: String [] = []
watch(() => route.path, (val) => {
if (visitedRoute.indexOf(val) === -1) {
visitedRoute.push(val)
handleTabsEdit(undefined, 'add', val)
} else {
editableKey.value = editableTabs.value.filter((tab) => tab.title === val)[0].key
}
})
const handleTabsEdit = (
targetKey: string | undefined,
action: 'remove' | 'add',
newTabTitle: string | undefined
) => {
if (action === 'add') {
const newTabName = `${++tabIndex}`
editableTabs.value.push({
title: newTabTitle,
key: newTabName,
})
editableKey.value = newTabName
} else if (action === 'remove') {
const tabs = editableTabs.value
let activeKey = editableKey.value
if (activeKey === targetKey) {
tabs.forEach((tab, index) => {
if (tab.key === targetKey) {
// 同步刪除visitedRoute數(shù)組
let includeIndex = visitedRoute.indexOf(tab.title)
visitedRoute.splice(includeIndex, 1)
// 重置active tab
const nextTab = tabs[index + 1] || tabs[index - 1]
if (nextTab) {
activeKey = nextTab.name
router.push({
path: nextTab.title
})
}
// /頁(yè)簽關(guān)閉時(shí),重置store中的exclude數(shù)組,如['/el/cascader/2']
const keepAliveStore = useKeepAlive()
let exclude: string[] = [tab.title]
keepAliveStore.setExclude(exclude)
}
})
}
editableKey.value = activeKey
editableTabs.value = tabs.filter((tab) => tab.key !== targetKey)
}
}
const tabChange = (activeKey: string) => {
let path = editableTabs.value.filter((tab) => tab.key === activeKey)[0].title
router.push({
path
})
}
</script>
const keepAliveStore = useKeepAlive()
const excludes = computed(() => {
return keepAliveStore.exclude
})
<router-view v-slot="{ Component }">
<keep-alive :max="10" :exclude="excludes">
<component :is="formatComponentInstance(Component, $route?.path)" :key="$route.path"/>
</keep-alive>
</router-view>
在調(diào)試中出現(xiàn)以下報(bào)錯(cuò):
Uncaught (in promise) TypeError: parentComponent.ctx.deactivate is not a function 報(bào)錯(cuò)的原因是因?yàn)槲覍⑸厦娴姆庋b簡(jiǎn)化為以下腳本, 導(dǎo)致同一個(gè)path兩次產(chǎn)生的組件vnode的type不一樣。為什么要簡(jiǎn)化呢, 因?yàn)榭紤]到wrapperMap會(huì)有一定的內(nèi)存消耗。
const formatComponentInstance = (component : Component, path:string ) => {
let wrapper = {
name: path,
render(){
return h(component) // h的第一個(gè)參數(shù)可以是字符串,也可以是一個(gè)組件定義;h返回的是一個(gè)虛擬dom
}
}
return h(wrapper)
}
在vue源碼中的isSameVNodeType中的n1.type === n2.type的判斷中, 組件轉(zhuǎn)化的vnode的type是一個(gè)對(duì)象,對(duì)于相同的route.path, 每次通過(guò)上面的封裝腳本產(chǎn)生新的組件實(shí)例,會(huì)導(dǎo)致每次產(chǎn)生的vnode的type對(duì)象不是同一個(gè)對(duì)象,導(dǎo)致n1.type不等于n2.type, isSameVNodeType返回false,會(huì)卸載n1,卸載時(shí)就產(chǎn)生了上面的報(bào)錯(cuò)。
function isSameVNodeType(n1, n2) {
if (n2.shapeFlag & 6 && hmrDirtyComponents.has(n2.type)) {
n1.shapeFlag &= ~256;
n2.shapeFlag &= ~512;
return false;
}
return n1.type === n2.type && n1.key === n2.key;
}
以上就是vue3手動(dòng)刪除keepAlive緩存的方法的詳細(xì)內(nèi)容,更多關(guān)于vue3刪除keepAlive緩存的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Vue實(shí)現(xiàn)路由跳轉(zhuǎn)的3種方式超詳細(xì)分解
Vue.js是一款流行的前端JavaScript框架,它提供了多種方式來(lái)實(shí)現(xiàn)路由跳轉(zhuǎn),下面這篇文章主要給大家介紹了關(guān)于Vue實(shí)現(xiàn)路由跳轉(zhuǎn)的3種方式,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12
vue離開(kāi)當(dāng)前頁(yè)面觸發(fā)的函數(shù)代碼
這篇文章主要介紹了vue離開(kāi)當(dāng)前頁(yè)面觸發(fā)的函數(shù)代碼,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
Vue 實(shí)現(xiàn)從小到大的橫向滑動(dòng)效果詳解
這篇文章主要介紹了Vue 實(shí)現(xiàn)從小到大的橫向滑動(dòng)效果,結(jié)合實(shí)例形式詳細(xì)分析了vue.js橫向漸變滑動(dòng)效果的實(shí)現(xiàn)步驟、相關(guān)操作技巧與注意事項(xiàng),需要的朋友可以參考下2019-10-10
vue+Element-ui前端實(shí)現(xiàn)分頁(yè)效果
這篇文章主要為大家詳細(xì)介紹了vue+Element-ui前端實(shí)現(xiàn)分頁(yè)效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11
vue自定義指令實(shí)現(xiàn)元素滑動(dòng)移動(dòng)端適配及邊界處理
這篇文章主要為大家介紹了vue自定義指令實(shí)現(xiàn)元素滑動(dòng)移動(dòng)端適配及邊界處理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09
詳解Vue基于vue-quill-editor富文本編輯器使用心得
這篇文章主要介紹了Vue基于vue-quill-editor富文本編輯器使用心得,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2019-01-01

