vue中使用keep-alive動(dòng)態(tài)刪除已緩存組件方式
項(xiàng)目場(chǎng)景
在做后臺(tái)管理系統(tǒng)的時(shí)候,有這樣一個(gè)需求:
后臺(tái)的界面如下:

點(diǎn)擊左邊的菜單,會(huì)在右邊的頂部生成一個(gè)個(gè)tag導(dǎo)航標(biāo)簽。當(dāng)打開(kāi)多個(gè)tag頁(yè)時(shí),用戶可以在多個(gè)tag之間進(jìn)行切換。需要在新增,修改頁(yè)面切換tag時(shí)候,保留之前的信息,不進(jìn)行頁(yè)面的刷新。
問(wèn)題描述
經(jīng)過(guò)查詢vue文檔,可以使用keep-alive實(shí)現(xiàn)標(biāo)簽路由緩存,實(shí)現(xiàn)方式如下:
在路由配置的meta中添加keepAlive,如下:
{
path: '/actdebt',
component: Layout,
redirect: '/actdebt/add',
children: [
{
path: 'add',
name: 'XXX新增配置',
meta: {
keepAlive: true,
},
component: () =>
import (/* webpackChunkName: "page" */'@/views/bankact/actdebt/add')
}]
},
然后在頁(yè)面中使用v-if做判斷,并且加上key
<keep-alive>
<router-view :key="$route.fullPath" class="avue-view"
v-if="$route.meta.keepAlive" />
</keep-alive>
<router-view class="avue-view" v-if="!$route.meta.keepAlive" />
使用上面這種方式解決了修改不同記錄的緩存問(wèn)題,因?yàn)椴煌涗浀膄ullPath 不一樣,這樣的話key就會(huì)不一樣。
但是對(duì)于新增和修改同一條記錄還是有緩存問(wèn)題。例如新增一條記錄保存成功后,下次在打開(kāi)新增頁(yè)面,還是緩存有之前的記錄。
修改頁(yè)面也是的,修改同一條記錄保存成功后,再次打開(kāi)可能還是會(huì)有之前的修改數(shù)據(jù)。
解決方案
要解決上面這種問(wèn)題我想到的解決方案為:在不同的tag導(dǎo)航欄切換的時(shí)候,保留緩存數(shù)據(jù)。當(dāng)關(guān)閉tag導(dǎo)航欄或者關(guān)閉頁(yè)面的時(shí)候,清除緩存。
清除緩存可以在組件里面的deactivated鉤子函數(shù)調(diào)用this.$destroy();但是發(fā)現(xiàn)下次打開(kāi)這個(gè)頁(yè)面的時(shí)候,新的組件不會(huì)被緩存了。
可以利用keep-alive的include,新打開(kāi)標(biāo)簽時(shí),把當(dāng)前組件名加入到include數(shù)組里,關(guān)閉標(biāo)簽時(shí)從數(shù)組中刪除關(guān)閉標(biāo)簽的組件名就可以了。
Include里面的值必須和組件的name屬性保持一致,如下:

但是如果我同一個(gè)組件加載了兩次,一個(gè)需要緩存,一個(gè)不需要緩存。但是他們的name卻是一樣的,還是無(wú)法解決問(wèn)題。
所以是否可以重寫(xiě)keep-alive源碼,使include可以按照路由地址匹配,而不是按照組件的name匹配。完整的代碼如下:
新建keepAlive.js文件

/**
* base-keep-alive 主要解決問(wèn)題場(chǎng)景:多級(jí)路由緩存
* 前提:保證動(dòng)態(tài)路由生成的route name 值都聲明了且唯一
* 基于以上對(duì)keep-alive進(jìn)行以下改造:
* 1. 組件名稱獲取更改為路由名稱
* 2. cache緩存key也更改為路由名稱
* 3. pruneCache
*/
const _toString = Object.prototype.toString
function isRegExp(v) {
return _toString.call(v) === '[object RegExp]'
}
export function remove(arr, item) {
if (arr.length) {
const index = arr.indexOf(item)
if (index > -1) {
return arr.splice(index, 1)
}
}
}
/**
* 1. 主要更改了 name 值獲取的規(guī)則
* @param {*} opts
*/
function getComponentName(opts) {
// return opts && (opts.Ctor.options.name || opts.tag)
return this.$route.path
}
function isDef(v) {
return v !== undefined && v !== null
}
function isAsyncPlaceholder(node) {
return node.isComment && node.asyncFactory
}
function getFirstComponentChild(children) {
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
const c = children[i]
if (isDef(c) && (isDef(c.componentOptions) || isAsyncPlaceholder(c))) {
return c
}
}
}
}
function matches(pattern, name) {
if (Array.isArray(pattern)) {
return pattern.indexOf(name) > -1
} else if (typeof pattern === 'string') {
return pattern.split(',').indexOf(name) > -1
} else if (isRegExp(pattern)) {
return pattern.test(name)
}
/* istanbul ignore next */
return false
}
function pruneCache(keepAliveInstance, filter) {
const { cache, keys, _vnode } = keepAliveInstance
for (const key in cache) {
const cachedNode = cache[key]
if (cachedNode) {
// ------------ 3. 之前默認(rèn)從router-view取儲(chǔ)存key值, 現(xiàn)在改為路由name, 所以這里得改成當(dāng)前key
// const name = getComponentName.call(keepAliveInstance, cachedNode.componentOptions)
const name = key
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode)
}
}
}
}
function pruneCacheEntry(
cache,
key,
keys,
current
) {
const cached = cache[key]
if (cached && (!current || cached.tag !== current.tag)) {
cached.componentInstance.$destroy()
}
cache[key] = null
remove(keys, key)
}
const patternTypes = [String, RegExp, Array]
export default {
name: 'keep-alive',
// abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created() {
this.cache = Object.create(null)
this.keys = []
},
destroyed() {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted() {
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.default
const vnode = getFirstComponentChild(slot)
const componentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// check pattern
const name = getComponentName.call(this, componentOptions)
// ---------- 對(duì)于沒(méi)有name值得設(shè)置為路由得name, 支持vue-devtool組件名稱顯示
if (!componentOptions.Ctor.options.name) {
vnode.componentOptions.Ctor.options.name
}
const { include, exclude } = this
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
const { cache, keys } = this
// ------------------- 儲(chǔ)存的key值, 默認(rèn)從router-view設(shè)置的key中獲取
// const key = vnode.key == null
// ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
// : vnode.key
// ------------------- 2. 儲(chǔ)存的key值設(shè)置為路由中得name值
const key = name
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
然后在main.js中引入該組件,使組件可以全局使用

在頁(yè)面直接使用BaseKeepAlive:
<BaseKeepAlive :include="cachetags">
<router-view :key="$route.fullPath" class="avue-view" />
</BaseKeepAlive>
cachetags 方法就是新打開(kāi)標(biāo)簽時(shí),把當(dāng)前組件名加入到include數(shù)組里,關(guān)閉標(biāo)簽時(shí)從數(shù)組中刪除關(guān)閉標(biāo)簽,源碼如下:
computed: {
...mapGetters(['isLock', "tagList",'isCollapse', 'website']),
cachetags(){
let list=[]
for(let item of this.tagList){
if(!validatenull(item.keepalive)&&item.keepalive){
list.push(item.value)
}
}
return list.join(',')
}
},
方法中的tagList就是導(dǎo)航欄當(dāng)前打開(kāi)相應(yīng)的tag集合如下圖所示

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
webpack搭建vue環(huán)境時(shí)報(bào)錯(cuò)異常解決
這篇文章主要介紹了webpack搭建vue環(huán)境時(shí)報(bào)錯(cuò)異常解決,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09
Vue項(xiàng)目移動(dòng)端滾動(dòng)穿透問(wèn)題的實(shí)現(xiàn)
這篇文章主要介紹了Vue項(xiàng)目移動(dòng)端滾動(dòng)穿透問(wèn)題的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-05-05
Vue-CLI 3 scp2自動(dòng)部署項(xiàng)目至服務(wù)器的方法
這篇文章主要介紹了Vue-CLI 3 scp2自動(dòng)部署項(xiàng)目至服務(wù)器的方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07
vue創(chuàng)建項(xiàng)目卡住不動(dòng),vue?create?project卡住不動(dòng)的解決
這篇文章主要介紹了vue創(chuàng)建項(xiàng)目卡住不動(dòng),vue?create?project卡住不動(dòng)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-10-10
vue 使用iView組件中的Table實(shí)現(xiàn)定時(shí)自動(dòng)滾動(dòng)效果
要在css中設(shè)置table的高度,使數(shù)據(jù)過(guò)多時(shí)出現(xiàn)滾動(dòng)條,將縱向設(shè)置為overflow-y: auto;橫向設(shè)置隱藏 overflow-x: hidden,接下來(lái)通過(guò)本文介紹vue使用iView組件中的Table實(shí)現(xiàn)定時(shí)自動(dòng)滾動(dòng)效果,需要的朋友可以參考下2024-05-05
vue+axios實(shí)現(xiàn)文件上傳的實(shí)時(shí)進(jìn)度條
最近用vue寫(xiě)上傳的時(shí)候,遇到一個(gè)需求就是頁(yè)面上展示上傳的進(jìn)度條,之后寫(xiě)過(guò)一次,但是用的是假交互,直接從0-100,今天分享一下用axios自帶的onUploadProgress來(lái)完成這個(gè)小需求,感興趣的朋友可以參考下2024-01-01
vue動(dòng)態(tài)添加store、路由和國(guó)際化配置方式
這篇文章主要介紹了vue動(dòng)態(tài)添加store、路由和國(guó)際化配置方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
vue如何通過(guò)props方式在子組件中獲取相應(yīng)的對(duì)象
這篇文章主要介紹了vue如何通過(guò)props方式在子組件中獲取相應(yīng)的對(duì)象,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
Vue + Scss 動(dòng)態(tài)切換主題顏色實(shí)現(xiàn)換膚的示例代碼
這篇文章主要介紹了Vue + Scss 動(dòng)態(tài)切換主題顏色實(shí)現(xiàn)換膚的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-04-04

