背景:在 B 端系統中,爲了使用方便咱們會在頁面設計中加上標籤頁相似瀏覽器上方標籤頁的功能,爲了使用體驗更加接近瀏覽器標籤頁,咱們須要針對路由進行緩存。本文主要介紹 Vue 項目針對不一樣業務場景如何利用 keep-alive 來實現標籤頁動態緩存。html
keep-alive 是一個抽象組件,不會和子組件創建父子關係,也不會做爲節點渲染到頁面上。vue
關於抽象組件 Vue 的文檔沒有提這個概念,它有一個屬性 abstract 爲 true,在抽象組件的生命週期過程當中,咱們能夠對包裹的子組件監聽的事件進行攔截,也能夠對子組件進行 Dom 操做,從而能夠對咱們須要的功能進行封裝,而不須要關心子組件的具體實現。除了kepp-alive還有<transition><transition-group>等。node
keep-alive 的模式下多了 activated 這個生命週期函數, keep-alive 的聲明週期執行:算法
created-> mounted-> activated,退出時觸發 deactivated 當再次進入(前進或者後退)時,只觸發 activated。vuex
keep-alive 是由 render 函數決定渲染結果,在開頭會獲取插槽內的子元素,調用 getFirstComponentChild 獲取到第一個子元素的 VNode。後端
const slot = this.$slots.default
const vnode: VNode = getFirstComponentChild(slot)
複製代碼
接着判斷當前組件是否符合緩存條件,組件名與 include 不匹配或與 exclude 匹配都會直接退出並返回 VNode,不走緩存機制。api
// check pattern
const name: ?string = getComponentName(componentOptions)
const { include, exclude } = this
if (
// not included
(include && (!name || !matches(include, name))) ||
// excluded
(exclude && name && matches(exclude, name))
) {
return vnode
}
複製代碼
匹配條件經過會進入緩存機制的邏輯,若是命中緩存,從 cache 中獲取緩存的實例設置到當前的組件上,並調整 key 的位置將其放到最後(LRU 策略)。 若是沒命中緩存,將當前 VNode 緩存起來,並加入當前組件的 key。若是緩存組件的數量超出 max 的值,即緩存空間不足,則調用 pruneCacheEntry 將最舊的組件從緩存中刪除,即 keys[0] 的組件。以後將組件的 keepAlive 標記爲 true,表示它是被緩存的組件。數組
LRU 緩存策略:從內存中找出最久未使用的數據置換新的數據.算法根據數據的歷史訪問記錄來進行淘汰數據,其核心思想是若是數據最近被訪問過,那麼未來被訪問的概率也更高。瀏覽器
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 {
cache[key] = vnode
keys.push(key)
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
複製代碼
pruneCacheEntry 負責將組件從緩存中刪除,它會調用組件 $destroy 方法銷燬組件實例,緩存組件置空,並移除對應的 key。緩存
function pruneCacheEntry (
cache: VNodeCache,
key: string,
keys: Array<string>,
current?: VNode
) {
const cached = cache[key]
if (cached && (!current || cached.tag !== current.tag)) {
cached.componentInstance.$destroy()
}
cache[key] = null
remove(keys, key)
}
複製代碼
通常採用在 router 的 meta 屬性裏增長一個 keepAlive 字段,而後在父組件或者根組件中,根據 keepAlive 字段的狀態使用 keep-alive 標籤,實現對路由的緩存:
<keep-alive>
<router-view v-if="$route.meta.keepAlive" />
</keep-alive>
<router-view v-if="!$route.meta.keepAlive" />
複製代碼
使用 vuex 配合 exclude 和 include,經過 include 和 exclude 決定那些組件進行緩存。注意這裏說的是組件,而且 cachedView 數組存放的是組件的名字,以下:
<keep-alive :include="$store.state.keepAlive.cachedView">
<router-view></router-view>
</keep-alive>
複製代碼
在 SPA 應用中用戶但願在 Tab 多個頁面來回切換的時候,不要丟失查詢的結果,關閉後清除緩存。
以下圖:
指望是用戶在切換 Tab 時 頁面時緩存的,當用戶關閉 Tab ,從新從左側菜單打開時是不緩存。
這樣是持久緩存了整個頁面,問題也就出現當用戶經過 Tab 關閉頁面而後從左側打開菜單時是緩存的頁面,這個不符合平常使用習慣,因此爲了解決數據新鮮度的問題能夠在 activated 觸發查詢請求就能保證數據的新鮮度。
activated(){
getData()
}
複製代碼
可是使用後發現因爲你切換 Tab 時每次都會請求數據,可是若是項目的數據量有很大頻繁請求會給後端形成很大壓力 。
版本一須要頻繁拉去數據致使此方案已不合適只能動態緩存組件方案。
<keep-alive :include="cachedViews">
<router-view :key="key"></router-view>
</keep-alive>
複製代碼
其中 cachedViews 是經過監聽路由動態增長刪除維護要緩存的組件名稱(因此組件名稱不要重複)數組:
const state = {
cachedViews: [],
}
const mutations = {
ADD_VIEWS: (state, view) => {
if (state.cachedViews.includes(view.name)) return
state.cachedViews.push(view.name)
},
DEL_CACHED_VIEW: (state, view) => {
const index = state.cachedViews.indexOf(view.name)
index > -1 && state.cachedViews.splice(index, 1)
},
}
const actions = {
addCachedView({ commit }, view) {
commit('ADD_VIEWS', view)
},
deleteCachedView({ commit }, view) {
commit('DEL_CACHED_VIEW', view)
},
}
export default {
namespaced: true,
state,
mutations,
actions,
}
複製代碼
經過監聽路由變化:
watch: {
'$route'(newRoute) {
const { name } = newRoute
const cacheRout = this.ISCACHE_MAP[name] || []
cacheRout.map((item) => {
store.dispatch('cached/addCachedView', { name: item })
})
},
},
當經過 Tab 關閉頁面時清除組件名稱:
closeTag(newRoute) {
const { name } = newRoute
const cacheRout = this.ISCACHE_MAP[name] || []
cacheRout.map((item) => {
store.dispatch('cached/deleteCachedView', { name: item })
})
}
複製代碼
可是在遇到嵌套路由時在層級不一樣的 router-view 中切換 Tab 會出現緩存數據失效的問題,沒法緩存組件,嵌套路由以下:
經過維護兩套數據,一套嵌套給左側菜單,一套扁平化後註冊路由,改造後的路由:
經過上面 keep-alive 解析能夠知道,keep-alive就是把經過 include 匹配的組件的 vnode,放到 keep-alive 組件的一個 cache 對象中,下次渲染時,若是能在這裏面找到,就直接渲染vnode。因此把這個 cache 對象,放到全局去(全局變量或者 vuex),這樣我就能夠不用緩存 ParnetView 也能緩存其指定的子組件了。
import Vue from 'vue'
const cache = {}
const keys = []
export const removeCacheByName = (name) => {/* 省略移除代碼 */}
export default Object.assign({}, Vue.options.components.KeepAlive, {
name: 'NewKeepAlive',
created() {
this.cache = cache
this.keys = keys
},
destroyed() {},
})
複製代碼
從上文能夠知道 keep-alive 是從 cache 中獲取緩存的實例設置到當前的組件上,key 是組件的名稱,能夠經過改造 getComponentName 方法,組件名稱獲取更改成路由名稱使其緩存的映射關係只與 route name 值有關係。
function getComponentName(opts) {
return this.$route.name
}
複製代碼
cache 緩存 key 也更改成路由名稱。
文|揣歪
關注得物技術,攜手走向技術的雲端