需求
單頁面應(yīng)用中,用戶進(jìn)入表單填寫頁面,需要初始化表單內(nèi)容,填寫過程中可能涉及到地圖選點或者列表選擇等操作,需要到新的頁面選擇并回調(diào)顯示。
此時我們需要緩存表單填寫頁面實例,當(dāng)退出表單填寫或提交完表單內(nèi)容之后,需要銷毀當(dāng)前表單實例,下次進(jìn)入重新進(jìn)行初始化
思考
說到 Vue 緩存,我們肯定首先選擇官方提供的緩存方案 keep-alive 內(nèi)置組件來實現(xiàn)。
keep-alive 組件提供給我們緩存組件的能力,可以完整的保存當(dāng)前組件的狀態(tài),這幫了我們很大的忙
但實際業(yè)務(wù)場景中,我們很多時候是按需緩存頁面的,就像 App 開發(fā)那樣,每個頁面都是單獨的一個頁面實例,由于 Vue Router 的限制,每個頁面有固定的一個 path,所以導(dǎo)致每次訪問這個 path 都會命中同一個組件實例
這個時候可能會有小伙伴說
誒,不是可以用
activated來進(jìn)行頁面更新或者處理嗎?
沒錯,是可以這樣,但是,有些操作是 mounted 里面要做,有些需要放到 activated 里面更新,代碼要處理很多進(jìn)入頁面的操作,就很麻煩啊。
此時就有兩個思考方向:
- 在必要的時候清除掉緩存頁面的實例
- 每次 push 頁面的時候,保證當(dāng)前頁面是全新的實例對象,和
App頁面棧相同
第二種方案可以比較物理的解決需求中的問題,但是需要改動的地方很多,比如 Vue Router 中路由切換的時候,是否采用動態(tài)生成 path ,確保當(dāng)前頁面實例不唯一,而且我們也要做好自己的頁面棧管理,類似于 iOS 中的 UINavigationController ,以便于及時清理棧中緩存的頁面實例
因為改動比較大,而且需要大量測試,所以最后還是選擇在方案一的方向進(jìn)行探索和嘗試。
嘗試
1. 手動操作 keep-alive 組件的 cache 數(shù)組
// Vue 2 keep-alive 部分源碼片段
const { cache, keys } = this;
const key: ?string =
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;
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance;
// make current key freshest
remove(keys, key);
keys.push(key);
} else {
// delay setting the cache until update
this.vnodeToCache = vnode;
this.keyToCache = key;
}
通過路由守衛(wèi)在特定的情況下刪除 cache 數(shù)組中的頁面實例,同時 destory 當(dāng)前實例
removeKeepAliveCacheForVueInstance(vueInstance) {
let key =
vueInstance.$vnode.key ??
vueInstance.$vnode.componentOptions.Ctor.cid + (vueInstance.$vnode.componentOptions.tag ? `::${vueInstance.$vnode.componentOptions.tag}` : "");
let cache = vueInstance.$vnode.parent.componentInstance.cache;
let keys = vueInstance.$vnode.parent.componentInstance.keys;
if (cache[key]) {
vueInstance.$destroy();
delete cache[key];
let index = keys.indexOf(key);
if (index > -1) {
keys.splice(index, 1);
}
}
}
這種方案比較繁瑣,但由于是直接操作 cache 數(shù)組,可能會產(chǎn)生一些預(yù)期外的泄漏問題或者運行問題,雖然我自己嘗試的時候沒有發(fā)現(xiàn)。。
在 Vue 3 中我也嘗試去尋找對應(yīng)的 cache 數(shù)組,還真被我找到了,但是 Vue 3 源碼中對于 cache 數(shù)組的操作權(quán)限僅限于開發(fā)環(huán)境
// Vue 3 KeepAlive 組件片段
if (__DEV__ || __FEATURE_PROD_DEVTOOLS__) {
;(instance as any).__v_cache = cache
}
部署生產(chǎn)環(huán)境之后就沒辦法通過 instance.__v_cache 來獲取 cache 數(shù)組了,所以這種方案到 Vue 3 就沒辦法進(jìn)行下去啦。
于是乎,就有了第二個嘗試
2. exclude 大法好
之前接觸 keep-alive 所有注意力都放在 include 這個屬性上面,其實 exclude 屬性同樣重要,而且效果和我們直接刪除 cache 數(shù)組異曲同工。
// Vue 3 KeepAlive 組件片段
if (
(include && (!name || !matches(include, name))) ||
(exclude && name && matches(exclude, name))
) {
current = vnode
return rawVNode
}
如果 exclude 里面有值,那么就返回當(dāng)前新的實例不從 cache 里面獲取。而且 exclude 的優(yōu)先級是高于 include 的。
利用這一點,我們就可以通過操作 exclude 中的內(nèi)容,來達(dá)到控制緩存頁面的效果。
而且 exclude 在 Vue 3 中的控制更為方便,只需要定義一個全局的 exclude 響應(yīng)式變量就可以隨處操作了,清除的具體方式取決于業(yè)務(wù)流程
export const excludes = ref<string[]>([]);
// 需要刪除的時候
export function removeKeepAliveCache(name: string) {
excludes.value.push(name);
}
// 需要恢復(fù)緩存的時候
export function resetKeepAliveCache(name: string) {
excludes.value = excludes.value.filter((item) => item !== name);
}
Demo
這里提供一個小 demo 演示一下緩存清除效果:
https://ztstory.github.io/vue-composition-demo/#/
流程:
Index與Input為緩存頁面Input返回到Index時清除Input緩存,重新進(jìn)入Input頁面激活緩存