Vue.js設(shè)計與實現(xiàn)18-KeepAlive的原理與實現(xiàn)
1.寫在前面
前面文章介紹了Vue.js通過渲染器實現(xiàn)組件化的能力,介紹了有狀態(tài)組件和無狀態(tài)組件的構(gòu)造與實現(xiàn),還有異步組件對于框架的意義。本文將主要介紹Vue.js的重要內(nèi)置組件和模塊--KeepAlive組件。
2.KeepAlive組件
KeepAlive字面意思理解就是保持鮮活,就是建立持久連接的意思,可以避免組件或連接頻繁地創(chuàng)建和銷毀。
<template>
<KeepAlive>
<Tab v-if="currentTab === 1"/>
<Tab v-if="currentTab === 2"/>
<Tab v-if="currentTab === 3"/>
</KeepAlive>
</template>
在上面代碼中,會根據(jù)currentTab變量的值頻繁切換Tab組件,會導(dǎo)致不停地卸載和重建對應(yīng)的Tab組件,為了避免因此產(chǎn)生的性能開銷,可以使用KeepAlive組件保持組件的鮮活。那么KeepAlive組件是如何保持組件的鮮活的,其實就會對組件進行緩存管理,避免組件頻繁的卸載和重建。
其實,就是通過一個隱藏的組件緩存容器,將組件需要的時候?qū)⑵浞诺饺萜骼?,在需要重建使用的時候?qū)⑵淙〕?,這樣對于用戶感知是進行了“卸載”和“重建”組件。在組件搬運到緩存容器和搬出,就是對應(yīng)組件的生命周期activated和deactivated。
3.組件的失活和激活
那么,應(yīng)該如何實現(xiàn)組件的緩存管理呢?
const KeepAlive = {
// keepAlive組件的標(biāo)識符
_isKeepAlive:true,
setup(props,{slots}){
//緩存容器
const cache = new Map();
const instance = currentInstance;
const { move, createElement } = instance.keepAliveCtx;
//隱藏容器
const storageContainer = createElement("div");
instance._deActivate = (vnode)=>{
move(vnode, storageContainer)
};
instance._activate = (vnode, container, anchor)=>{
move(vnode, container, anchor)
};
return ()=>{
let rawNode = slots.default();
// 非組件的虛擬節(jié)點無法被keepAlive
if(typeof rawNode.type !== "object"){
return rawNode;
}
//在掛在時先獲取緩存的組件vnode
const cacheVNode = cache.get(rawNode.type);
if(cacheVNode){
rawVNode.component = cacheVNode.component;
rawVNode.keptAlive = true;
}else{
cache.set(rawVNode.type, rawVNode);
}
rawVNode.shouldKeepAlive = true;
rawVNode.keepAliveInstance = instance;
// 渲染組件vnode
return rawVNode
}
}
}
在上面代碼中,KeepAlive組件本身不會渲染額外的內(nèi)容,渲染函數(shù)只返回被KeepAlive的組件,被稱為“內(nèi)部組件”,KeepAlive會在“內(nèi)部組件”的Vnode對象上添加標(biāo)記屬性,便于渲染器執(zhí)行特定邏輯。
- shouldKeepAlive屬性會被添加到“內(nèi)部組件”的vnode對象上,當(dāng)渲染器卸載“內(nèi)部組件”時,可以通過檢查屬性得知“內(nèi)部組件”是否需要被KeepAlive。
- keepAliveInstance:內(nèi)部組件的vnode對象會持有keepAlive組件實例,在unmount函數(shù)中通過keepAliveInstance訪問_deactivate函數(shù)。
- keptAlive:內(nèi)部組件已被緩存則添加keptAlive標(biāo)記,判斷內(nèi)部組件重新渲染時是否需要重新掛載還是激活。
function unmount(vnode){
if(vnode.type === Fragment){
vnode.children.forEach(comp=>unmount(comp));
return;
}else if(typeof vnode.type === "object"){
if(vnode.shouldKeepAlive){
vnode.keepAliveInstance._deactivate(vnode);
}else{
unmount(vnode.component.subTree);
}
return
}
const parent = vnode.el.parentVNode;
if(parent){
parent.removeChild(vnode.el);
}
}
組件失活的本質(zhì)是將組件所渲染的內(nèi)容移動到隱藏容器中,激活的本質(zhì)是將組件所要渲染的內(nèi)容從隱藏容器中搬運回原來的容器。
const { move, createElement } = instance.keepAliveCtx;
instance._deActivate = (vnode)=>{
move(vnode, storageContainer);
}
instance._activate = (vnode, container, anchor)=>{
move(vnode, container, anchor);
}
4.include和exclude
我們看到上面的代碼會對組件所有的"內(nèi)部組件"進行緩存,但是使用者又想自定義緩存規(guī)則,只對特定組件進行緩存,對此KeepAlive組件需要支持兩個props:include和exclude。
- include:用于顯式配置應(yīng)被緩存的組件
- exclude:用于顯式配置不應(yīng)該被緩存的組件
const cache = new Map();
const keepAlive = {
__isKeepAlive: true,
props:{
include: RegExp,
exclude: RegExp
},
setup(props, {slots}){
//...
return ()=>{
let rawVNode = slots.default();
if(typeof rawVNode.type !== "object"){
return rawVNode;
}
const name = rawVNode.type.name;
if(name && (
(props.include && !props.include.test(name)) ||
(props.exclude && props.include.test(name))
)){
//直接渲染內(nèi)部組件,不對其進行緩存操作
return rawVNode
}
}
}
}
上面代碼中,為了簡便闡述問題進行設(shè)置正則類型的值,在KeepAlive組件被掛載時,會根據(jù)"內(nèi)部組件"的名稱進行匹配,根據(jù)匹配結(jié)果判斷是否要對組件進行緩存。
5.緩存管理
在前面小節(jié)中使用Map對象實現(xiàn)對組件的緩存,Map的鍵值對分別對應(yīng)的是組件vnode.type屬性值和描述該組件的vnode對象。因為用于描述組件的vnode對象存在對組件實例的引用,對此緩存用于描述組件的vnode對象,等價于緩存了組件實例。
前面介紹的keepAlive組件實現(xiàn)緩存的處理邏輯是:
- 緩存存在時繼承組件實例,將描述組件的vnode對象標(biāo)記為keptAlive,渲染器不會重新創(chuàng)建新的組件實例
- 緩存不存在時,則設(shè)置緩存
但是,如果緩存不存在時,那么總是會設(shè)置新的緩存,這樣導(dǎo)致緩存不斷增加,會占用大量內(nèi)存。對此,我們需要設(shè)置個內(nèi)存閾值,在緩存數(shù)量超過指定閾值時需要對緩存進行修剪,在Vue.js中使用的是"最新一次訪問"策略。
"最新一次訪問"策略本質(zhì)上就是通過設(shè)置當(dāng)前訪問或渲染的組件作為最新一次渲染的組件,并且該組件在修剪過程中始終是安全的,即不會被修剪。
緩存實例中需要滿足固定的格式:
const _cache = new Map();
const cache: KeepAliveCache = {
get(key){
_cache.get(key);
},
set(key, value){
_cache.set(key, value);
},
delete(key){
_cache.delete(key);
},
forEach(fn){
_cache.forEach(fn);
}
}
6.寫在最后
本文簡單介紹了Vue.js中KeepAlive組件的設(shè)計與實現(xiàn)原理,可以實現(xiàn)對組件的緩存,避免組件實例不斷地銷毀和重建。KeepAlive組件卸載時渲染器并不會真實地把它進行卸載,而是將該組件搬運到另外一個隱藏容器里,從而使得組件能夠維持當(dāng)前狀態(tài)。在KeepAlive組件掛載時,渲染器將其從隱藏容器中搬運到原容器中。此外,我們還討論了KeepAlive組件的include和exclude自定義緩存,以及緩存管理。