【Vuejs】1156- 聊聊vue中的keep-alive
大前端??前端知識(shí)寶庫(kù)??堅(jiān)持日更
一、什么是keep-alive?
官方介紹就是:?包裹動(dòng)態(tài)組件時(shí),會(huì)緩存不活動(dòng)的組件實(shí)例,而不是銷毀它們。和??相似,?是一個(gè)抽象組件:它自身不會(huì)渲染一個(gè) DOM 元素,也不會(huì)出現(xiàn)在組件的父組件鏈中。
當(dāng)組件在??內(nèi)被切換時(shí),它的?mounted?和?unmounted?生命周期鉤子不會(huì)被調(diào)用,取而代之的是?activated?和?deactivated。
簡(jiǎn)單理解就是說(shuō)我們可以把一些不常變動(dòng)的組件或者需要緩存的組件用包裹起來(lái),這樣就會(huì)幫我們把組件保存在內(nèi)存中,而不是直接的銷毀,這樣做可以保留組件的狀態(tài)或避免多次重新渲染,以提高頁(yè)面性能
二、使用用法
我們先根據(jù)官方文檔來(lái)回顧一下組件的具體用法,如下:
組件可接收三個(gè)屬性:
Props: include?-?string | RegExp | Array。只有名稱匹配的組件會(huì)被緩存。exclude?-?string | RegExp | Array。任何名稱匹配的組件都不會(huì)被緩存。max?-?number | string。最多可以緩存多少組件實(shí)例。用法:
include?和?exclude?prop 允許組件有條件地緩存。二者都可以用逗號(hào)分隔字符串、正則表達(dá)式或一個(gè)數(shù)組來(lái)表示:
??
??<keep-alive?include="a,b">
????<component?:is="view">component>
??keep-alive>
??
??
??<keep-alive?:include="/a|b/">
????<component?:is="view">component>
??keep-alive>
??
??
??<keep-alive?:include="['a',?'b']">
????<component?:is="view">component>
??keep-alive>
匹配首先檢查組件自身的?name?選項(xiàng),如果?name?選項(xiàng)不可用,則匹配它的局部注冊(cè)名稱 (父組件?components?選項(xiàng)的鍵值)。匿名組件不能被匹配。
max表示最多可以緩存多少組件實(shí)例。一旦這個(gè)數(shù)字達(dá)到了,在新實(shí)例被創(chuàng)建之前,已緩存組件中最久沒(méi)有被訪問(wèn)的實(shí)例會(huì)被銷毀掉。
??<keep-alive?:max="10">
????<component?:is="view">component>
??keep-alive>
在這里簡(jiǎn)單介紹一個(gè)日常項(xiàng)目中有可能出現(xiàn)的場(chǎng)景并使用keep-alive來(lái)實(shí)現(xiàn)按需控制緩存 場(chǎng)景:當(dāng)我們從首頁(yè)–>列表頁(yè)–>商品詳情頁(yè)–>返回到列表頁(yè)(需要緩存)–>返回到首頁(yè)(需要緩存)–>再次進(jìn)入列表頁(yè)(不需要緩存)
在路由meta對(duì)象里定義兩個(gè)值:
keepAlive:這個(gè)路由是否需要緩存deepth:代表頁(yè)面之間的前進(jìn)后退的層級(jí)關(guān)系
???
???{
???????path:?'*',
???????name:?'Home',
???????component:?()?=>?import(/*?webpackPreload:?true?*/?'@/views/home'),
???????meta:?{
?????????keepAlive:?true,
?????????deepth:?1
???????}
?????},
?????{
???????path:?'/list',
???????name:?'list',
???????component:?()?=>?import('@/views/list'),
???????meta:?{
?????????keepAlive:?true,
?????????deepth:?2
???????}
?????},
?????{
???????path:?'/detail',
???????name:?'Detail',
???????component:?()?=>?import('@/views/detail'),
???????meta:?{
?????????keepAlive:?true,
?????????deepth:?3
???????}
?????},
?????
監(jiān)聽(tīng)路由動(dòng)態(tài)控制需要緩存的值
???//3x版本router-view不允許直接寫在keep-alive里面,需注意
???
?????<div?id="app">
???????<keep-alive?:include="include">
?????????<router-view?v-if="$route.meta.keepAlive"?/>
???????keep-alive>
???????<router-view?v-if="!$route.meta.keepAlive"?/>
?????div>
???</template>
???
???export?default?{
?????data()?{
???????return?{
?????????include:?[]
???????};
?????},
?????watch:?{
???????$route(to,?from)?{
?????????//?如果要to(進(jìn)入)的頁(yè)面是需要keepAlive緩存的,把name?push進(jìn)include數(shù)組中
?????????if?(to.meta.keepAlive)?{
???????????!this.include.includes(to.name)?&&?this.include.push(to.name);
?????????}
?????????//?如果?要?form(離開(kāi))?的頁(yè)面是?keepAlive緩存的,
?????????//?再根據(jù)?deepth?來(lái)判斷是前進(jìn)還是后退
?????????//?如果是后退:
?????????if?(from.meta.keepAlive?&&?to.meta.deepth????????????const?index?=?this.include.indexOf(from.name);
???????????index?!==?-1?&&?this.include.splice(index,?1);
?????????}
???????}
?????}
???};
???
以上場(chǎng)景在通過(guò)監(jiān)聽(tīng)路由,動(dòng)態(tài)的設(shè)置了在第一次進(jìn)入并回退回來(lái)時(shí)的緩存實(shí)現(xiàn),并在第二次進(jìn)入時(shí)重新開(kāi)始進(jìn)行新一輪緩存設(shè)置,實(shí)現(xiàn)動(dòng)態(tài)控制緩存。
三、實(shí)現(xiàn)
組件的定義位于源碼的?src/core/components/keep-alive.js?文件中,本文參考:https://unpkg.com/browse/[email protected]/src/core/components/keep-alive.js,感興趣的可以自行查看,下面只展示部分代碼。
const?patternTypes:?Array<Function>?=?[String,?RegExp,?Array]
export?default?{
??name:?'keep-alive',
??abstract:?true,
??props:?{
????include:?patternTypes,
????exclude:?patternTypes,
????max:?[String,?Number]
??},
??created?()?{
????this.cache?=?Object.create(null)
????this.keys?=?[]
??},
??destroyed?()?{
????for?(const?key?in?this.cache)?{
??????pruneCacheEntry(this.cache,?key,?this.keys)
????}
??},
??mounted?()?{
????this.$watch('include',?val?=>?{
??????pruneCache(this,?name?=>?matches(val,?name))
????})
????this.$watch('exclude',?val?=>?{
??????pruneCache(this,?name?=>?!matches(val,?name))
????})
??},
??render?()?{
????const?slot?=?this.$slots.default
????const?vnode:?VNode?=?getFirstComponentChild(slot)
????const?componentOptions:??VNodeComponentOptions?=?vnode?&&?vnode.componentOptions
????if?(componentOptions)?{
??????//?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
??????}
??????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)
????????}
??????}
??????vnode.data.keepAlive?=?true
????}
????return?vnode?||?(slot?&&?slot[0])
??}
}
開(kāi)始我們先從created鉤子開(kāi)始進(jìn)行分析:
created:
在?created?鉤子函數(shù)里定義并初始化了兩個(gè)屬性:?this.cache?和?this.keys。
created?()?{
????this.cache?=?Object.create(null)
????this.keys?=?[]
}
this.cache是一個(gè)對(duì)象,用來(lái)存儲(chǔ)需要緩存的組件。
this.keys是一個(gè)數(shù)組,用來(lái)存儲(chǔ)每個(gè)需要緩存的組件的key,即對(duì)應(yīng)this.cache對(duì)象中的鍵值。
destroyed:
當(dāng)組件被銷毀時(shí),此時(shí)會(huì)調(diào)用destroyed鉤子函數(shù),在該鉤子函數(shù)里會(huì)遍歷this.cache對(duì)象,然后將那些被緩存的并且當(dāng)前沒(méi)有處于被渲染狀態(tài)的組件都銷毀掉并將其從this.cache對(duì)象中刪除。如下:
destroyed?()?{
????for?(const?key?in?this.cache)?{
??????pruneCacheEntry(this.cache,?key,?this.keys)
????}
}
上面用到了pruneCacheEntry函數(shù):
function?pruneCacheEntry?(
??cache:?VNodeCache,
??key:?string,
??keys:?Array,
??current?:?VNode
)?{
??const?cached?=?cache[key]
??/*?判斷當(dāng)前沒(méi)有處于被渲染狀態(tài)的組件,將其銷毀*/
??if?(cached?&&?(!current?||?cached.tag?!==?current.tag))?{
????cached.componentInstance.$destroy()
??}
??cache[key]?=?null
??remove(keys,?key)
}
mounted:
在mounted鉤子函數(shù)中觀測(cè)?include?和?exclude?的變化,如下:
mounted?()?{
????this.$watch('include',?val?=>?{
??????pruneCache(this,?name?=>?matches(val,?name))
????})
????this.$watch('exclude',?val?=>?{
??????pruneCache(this,?name?=>?!matches(val,?name))
????})
??}
如果include?或exclude?發(fā)生了變化,即表示定義需要緩存的組件的規(guī)則或者不需要緩存的組件的規(guī)則發(fā)生了變化,那么就執(zhí)行pruneCache函數(shù)
function?pruneCache?(keepAliveInstance:?any,?filter:?Function)?{
??const?{?cache,?keys,?_vnode?}?=?keepAliveInstance
??for?(const?key?in?cache)?{
????const?cachedNode:??VNode?=?cache[key]
????if?(cachedNode)?{
??????const?name:??string?=?getComponentName(cachedNode.componentOptions)
??????if?(name?&&?!filter(name))?{
????????pruneCacheEntry(cache,?key,?keys,?_vnode)
??????}
????}
??}
}
在該函數(shù)內(nèi)對(duì)this.cache對(duì)象進(jìn)行遍歷,取出每一項(xiàng)的name值,用其與新的緩存規(guī)則進(jìn)行匹配,如果匹配不上,則表示在新的緩存規(guī)則下該組件已經(jīng)不需要被緩存,則調(diào)用pruneCacheEntry函數(shù)將這個(gè)已經(jīng)不需要緩存的組件實(shí)例先銷毀掉,然后再將其從this.cache對(duì)象中刪除。
render:
?為一個(gè)函數(shù)式組件。執(zhí)行組件渲染的時(shí)候,就會(huì)執(zhí)行到這個(gè)?render?函數(shù)
render?()?{
???/*?獲取默認(rèn)插槽中的第一個(gè)組件節(jié)點(diǎn)?*/
????const?slot?=?this.$slots.default
????const?vnode:?VNode?=?getFirstComponentChild(slot)
????const?componentOptions:??VNodeComponentOptions?=?vnode?&&?vnode.componentOptions
????if?(componentOptions)?{
??????/*?獲取該組件節(jié)點(diǎn)的名稱?*/
??????const?name:??string?=?getComponentName(componentOptions)?
??????/*?如果name與include規(guī)則不匹配或者與exclude規(guī)則匹配則表示不緩存,直接返回vnode?*/
??????const?{?include,?exclude?}?=?this
??????if?(
????????//?not?included
????????(include?&&?(!name?||?!matches(include,?name)))?||
????????//?excluded
????????(exclude?&&?name?&&?matches(exclude,?name))
??????)?{
????????return?vnode
??????}
???/*-----需要走緩存-----*/
??????const?{?cache,?keys?}?=?this
??????/*?獲取組件的key?*/
??????const?key:??string?=?vnode.key?==?null
??????????componentOptions.Ctor.cid?+?(componentOptions.tag???`::${componentOptions.tag}`?:?'')
????????:?vnode.key
??????/*?如果命中緩存,則直接從緩存中拿?vnode?的組件實(shí)例?*/
??????if?(cache[key])?{
????????vnode.componentInstance?=?cache[key].componentInstance
????????//?make?current?key?freshest?調(diào)整該組件key的順序,將其從原來(lái)的地方刪掉并重新放在最后一個(gè)?
????????remove(keys,?key)
????????keys.push(key)
??????}?else?{
????????/*?如果沒(méi)有命中緩存,則將其設(shè)置進(jìn)緩存?*/
????????cache[key]?=?vnode
????????keys.push(key)
????????//?prune?oldest?entry?如果配置了max并且緩存的長(zhǎng)度超過(guò)了this.max,則從緩存中刪除第一個(gè)
????????if?(this.max?&&?keys.length?>?parseInt(this.max))?{
??????????pruneCacheEntry(cache,?keys[0],?keys,?this._vnode)
????????}
??????}
??????vnode.data.keepAlive?=?true
????}
????return?vnode?||?(slot?&&?slot[0])
??}
獲取默認(rèn)插槽中的第一個(gè)組件節(jié)點(diǎn)。 由于我們也是在? ?標(biāo)簽內(nèi)部寫 DOM,所以可以先獲取到它的默認(rèn)插槽,然后再獲取到它的第一個(gè)子節(jié)點(diǎn)。?只處理第一個(gè)子元素,所以一般和它搭配使用的有?component?動(dòng)態(tài)組件或者是?router-view。獲取該組件節(jié)點(diǎn)的名稱,然后用組件名稱跟? include、exclude?中的匹配規(guī)則去匹配,如果組件名稱與?include?規(guī)則不匹配或者與?exclude?規(guī)則匹配,則表示不緩存該組件,直接返回這個(gè)組件的?vnode,否則的話走下一步緩存獲取組件的 key值:拿到key值后去this.cache對(duì)象中去尋找是否有該值,如果有則表示該組件有緩存,即命中緩存,直接從緩存中拿?vnode?的組件實(shí)例,此時(shí)重新調(diào)整該組件key的順序,將其從原來(lái)的地方刪掉并重新放在this.keys中最后一個(gè)。沒(méi)有繼續(xù)下一步表明該組件還沒(méi)有被緩存過(guò),則以該組件的 key為鍵,組件vnode為值,將其存入this.cache中,并且把key存入this.keys中。此時(shí)再判斷this.keys中緩存組件的數(shù)量是否超過(guò)了設(shè)置的最大緩存數(shù)量值this.max,如果超過(guò)了,則把第一個(gè)緩存組件刪掉最后設(shè)置? vnode.data.keepAlive = true?,最后將vnode返回
總結(jié)
上面介紹了Vue中的內(nèi)置組件組件以及組件的具體用法。同時(shí)也分析了組件的一些內(nèi)部原理,底下則是個(gè)人對(duì)于keep-alive的運(yùn)行的一些個(gè)人理解總結(jié):?keep-alive主要作用是緩存vnode,大概可以分為三個(gè)運(yùn)行階段
初始未存在緩存階段,在 created鉤子定義了用于保存vnode的cache對(duì)象以及保存緩存了的vnode列表keys,用于數(shù)據(jù)的存儲(chǔ)。mounted鉤子觀測(cè)監(jiān)聽(tīng)?include?和?exclude?的變化,進(jìn)行緩存vnode的變化更新,最后調(diào)用render進(jìn)行組件vnode的第一次緩存設(shè)置。 因?yàn)榫彺嬉恍┚唧w業(yè)務(wù)功能的組件vnode對(duì)于我們來(lái)說(shuō),什么時(shí)候開(kāi)始緩存、何時(shí)銷毀以及何時(shí)運(yùn)行render重新刷新,開(kāi)發(fā)者是沒(méi)有vnode的直接控制能力,所以需要定義一些屬性include、exclude、max來(lái)進(jìn)行一個(gè)判斷,用于更準(zhǔn)確的對(duì)需要緩存的vnode進(jìn)行控制處理。已緩存的更新階段,調(diào)用 render函數(shù)直接從cache對(duì)象返回已緩存的vnode,避免了多次的重新渲染,來(lái)提高頁(yè)面性能。銷毀階段, destroyed鉤子定義組件銷毀時(shí)清除那些被緩存的并且當(dāng)前沒(méi)有處于被渲染狀態(tài)的組件。銷毀組件時(shí),對(duì)于緩存的vnode對(duì)象,不清除的話應(yīng)該也會(huì)造成一些內(nèi)存上的占用或者內(nèi)存泄漏的問(wèn)題,所以在銷毀時(shí)需要進(jìn)行一個(gè)清除緩存的操作。
以上就是關(guān)于keep-alive的相關(guān)內(nèi)容,希望每個(gè)看完的同學(xué)都有自己收獲,完結(jié) ??????

回復(fù)“加群”與大佬們一起交流學(xué)習(xí)~
點(diǎn)擊“閱讀原文”查看 130+ 篇原創(chuàng)文章
