Vue 更改keep-alive原始碼,滿足條件性快取(多個頁籤之間切換快取,關閉頁籤重新開啟不快取)
阿新 • • 發佈:2021-02-08
應用場景
當系統開啟多個頁籤時,多個頁籤之間切換去做快取,關閉頁籤重新開啟該頁籤不快取。
解決方案
更改 vue keep-alive原始碼
1、製造一個唯一的key值,在路由引數裡面加一個時間戳
1.1 因keep-alive 使用元件名稱name值作為唯一的key值去做條件快取,但是該專案因書寫不規範導致元件的name值不是唯一的。在路由的引數
裡面增加一個時間戳 // 當點選系統左側選單的時候加時間戳 select(url) { //先把開啟的頁籤全部放到store裡面 //在開啟頁籤裡面找index原因是:找到已經開啟過的頁籤的index,避免開啟重複的多個頁籤 const isHas = this.$store.state.multiTabPages.findIndex((val)=>{ return val.name === url }) if(isHas === -1) { this.$router.push({name:url, query: { timeStr: new Date().getTime() }}); }else { //如果找到已經開啟過的這個頁籤,那就跳轉到它原來的最初開啟的頁籤頁面 this.$router.push({name:url, query: { timeStr: this.$store.state.multiTabPages[isHas].query.timeStr }}); } },
2、給自定義的keep-alive元件唯一的key值
2.1 從依賴vue包裡面找到keep-alive元件,拿到原始碼。在全域性的元件裡面建立一個自定義的keep-alive元件,注意元件名不要和vue自帶的keep-alive元件名稱相同會有衝突。 2.2 原始碼是用ts寫的,如果你的專案不支援ts,要先改成js,主要改的是render裡面的邏輯。 // keep-alive使用的是name值做匹配快取,如下: const name = getComponentName(componentOptions) const { include, exclude } = this if ( // not included (include && (!name || !matches(include, name))) || // excluded (exclude && name && matches(exclude, name)) ) { return vnode } const { cache, keys } = this const key = vnode.key == null // same constructor may get registered as different local components // so cid alone is not enough (#3269) ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '') : vnode.key 2.3 把關閉頁籤的路由資訊存入store中,在自定義的keep-alive元件中拿到關閉頁籤的路由資訊 const slot = this.$slots.default // 獲取預設插槽 const vnode = getFirstComponentChild(slot) // 獲取第一個子節點 const componentOptions = vnode && vnode.componentOptions if (componentOptions) { const { cache, keys } = this const route = this.$route const key = route.fullPath //拿到開啟的頁籤的唯一key值(在路由裡面加的時間戳和path組成的一個唯一的key值) // 拿到關閉頁籤的路由資訊 const removeRoute = this.$store.state.removeRoute || [] if(removeRoute && removeRoute.length > 0){ let tempKeys = JSON.parse(JSON.stringify(this.keys)) let diff = tempKeys.filter((val) => { return !removeRoute.includes(val)}) this.keys = JSON.parse(JSON.stringify(diff)) for (const key in this.cache) { // 在快取的cache物件裡面移除掉已關閉的頁籤的資訊 if(removeRoute.includes(key)) { delete this.cache[key]; } } } // 如果key對應的vnode存在,則更新key值 if (cache[key]) { vnode.componentInstance = cache[key].componentInstance remove(keys, key) keys.push(key) // 調整key排序 } else { // 否則將vnode存入快取 cache[key] = vnode keys.push(key) } vnode.data.keepAlive = true } return vnode || (slot && slot[0])
3、使用自定義的keep-alive元件
3.1 在專案做外層的渲染容器中使用
<multi-tab></multi-tab> //頁籤元件
<KeepAliveCostom> //自定義keep-alive元件
<Common :key="$route.fullPath" /> //頁面渲染的二級容器元件
</KeepAliveCostom>
頁面容器結構如圖
3.2 給自定義的keep-alive元件傳這個唯一的key值
綜上:根據製造出路由中的一個唯一key值來代替keep-alive元件中的使用name作為唯一的key值,實現keep-alive的條件性渲染
附上更改後的keep-alive原始碼(有刪減)
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)
}
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 isAsyncPlaceholder (node) {
return node.isComment && node.asyncFactory
}
function isDef (v) {
return v !== undefined && v !== null
}
function remove (arr, item) {
if (arr.length) {
const index = arr.indexOf(item)
if (index > -1) {
return arr.splice(index, 1)
}
}
}
export default {
name: 'keep-alive-custom',
abstract: true,
created () {
this.cache = Object.create(null)
this.keys = []
},
destroyed () {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
render () {
const slot = this.$slots.default // 獲取預設插槽
const vnode = getFirstComponentChild(slot) // 獲取第一個子節點
const componentOptions = vnode && vnode.componentOptions
if (componentOptions) {
const { cache, keys } = this
const route = this.$route
const key = route.fullPath //拿到開啟的頁籤的唯一key值
// 拿到關閉頁籤的路由資訊
const removeRoute = this.$store.state.removeRoute || []
if(removeRoute && removeRoute.length > 0){
let tempKeys = JSON.parse(JSON.stringify(this.keys))
let diff = tempKeys.filter((val) => { return !removeRoute.includes(val)})
this.keys = JSON.parse(JSON.stringify(diff))
for (const key in this.cache) {
// 在快取的cache物件裡面移除掉已關閉的頁籤的資訊
if(removeRoute.includes(key)) {
delete this.cache[key];
}
}
}
// 如果key對應的vnode存在,則更新key值
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
remove(keys, key)
keys.push(key) // 調整key排序
} else {
// 否則將vnode存入快取
cache[key] = vnode
keys.push(key)
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}