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

回復“加群”與大佬們一起交流學習~
點擊“閱讀原文”查看 130+ 篇原創(chuàng)文章
