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




































