04-vue3中keep-alive 實(shí)現(xiàn)原理解析
你好我是劉小灰,這是我的第 04 篇原創(chuàng)文章。
前言
最近在做項(xiàng)目中遇到一個(gè)很奇葩的問題,就是給路由設(shè)置 keep-alive 就是不生效,就在寫這篇文章之前,這個(gè) bug 還是沒得到解決,所以打算看看 keep-alive 的實(shí)現(xiàn)邏輯,看是否從源碼中得到些解決這個(gè)bug的啟發(fā)。
怎么去看源碼
為了高效,我們不能漫無目的的去看源碼,那樣會很痛苦且沒有任何收獲,所以我建議大家在看源碼的時(shí)候最好是帶著問題去看源碼,這樣會更有目的性,效率自然會大大提高。
從源碼中想得到什么
對于 keep-alive 在沒有看源碼之前我有以下幾個(gè)疑問
keep-alive緩存的是什么?vnode 嗎?keep-alive是把組件緩存到哪里了?keep-alive的工作流程是什么?
下面我們就來帶著問題來看源碼
首先 keep-alive 也是一個(gè)組件,我們先找到組件的實(shí)例,源碼如下所示:
const?KeepAliveImpl:?ComponentOptions?=?{
??name:?`KeepAlive`,
??__isKeepAlive:?true,
??props:?{
????include:?[String,?RegExp,?Array],
????exclude:?[String,?RegExp,?Array],
????max:?[String,?Number]
??},
??setup(props:?KeepAliveProps,?{?slots?}:?SetupContext)?{
??...
??}
}
首先我們可以看到 keep-alive 接受三個(gè)props分別是
- include 待緩存組件名稱
- exclude ?排除緩存的組件名稱
- max ? 允許最大緩存組件數(shù)
接下來我們具體看下 setup 函數(shù),如下所示:
setup(props:?KeepAliveProps,?{?slots?}:?SetupContext)?{
????//?獲取?keep-alive?實(shí)例?并保證一定存在
????const?instance?=?getCurrentInstance()!
????const?sharedContext?=?instance.ctx?as?KeepAliveContext
????if?(!sharedContext.renderer)?{
??????return?slots.default
????}
????//?map?用于緩存?組件的vnode
????const?cache:?Cache?=?new?Map()
????//?用于緩存組件的key
????const?keys:?Keys?=?new?Set()
????let?current:?VNode?|?null?=?null
????if?(__DEV__?||?__FEATURE_PROD_DEVTOOLS__)?{
??????;(instance?as?any).__v_cache?=?cache
????}
????...
??}
從代碼中我們可以看出 keep-alive 是使用 Map 來緩存組件的,后面的代碼是關(guān)于鉤子函數(shù)的執(zhí)行和對于緩存組件的維護(hù)。

watch 后面有一段非常重要的代碼就是cacheSubtree函數(shù),用來緩存組件
?const?cacheSubtree?=?()?=>?{
??????if?(pendingCacheKey?!=?null)?{
????????cache.set(pendingCacheKey,?getInnerChild(instance.subTree))
??????}
????}
我們再來看看 getInnerChild 返回的是什么,就證明了 keep-alive 緩存的是不是vnode
function?getInnerChild(vnode:?VNode)?{
??return?vnode.shapeFlag?&?ShapeFlags.SUSPENSE???vnode.ssContent!?:?vnode
}
可以看出 keep-alive 緩存的就是組件的vnode。
接下來我們再來看看 setup 的 return (渲染)函數(shù)
return?()?=>?{
??????//?key
??????pendingCacheKey?=?null
??????if?(!slots.default)?{
????????return?null
??????}
??????const?children?=?slots.default()
??????const?rawVNode?=?children[0]
??????if?(children.length?>?1)?{
????????if?(__DEV__)?{
??????????warn(`KeepAlive?should?contain?exactly?one?component?child.`)
????????}
????????current?=?null
????????return?children
??????}?else?if?(
????????!isVNode(rawVNode)?||
????????(!(rawVNode.shapeFlag?&?ShapeFlags.STATEFUL_COMPONENT)?&&
??????????!(rawVNode.shapeFlag?&?ShapeFlags.SUSPENSE))
??????)?{
????????current?=?null
????????return?rawVNode
??????}
??????let?vnode?=?getInnerChild(rawVNode)
??????console.log('vnode',?vnode?===?rawVNode)
??????//使用vnode的type當(dāng)做key
??????const?comp?=?vnode.type?as?ConcreteComponent
??????const?name?=?getComponentName(
????????isAsyncWrapper(vnode)
????????????(vnode.type?as?ComponentOptions).__asyncResolved?||?{}
??????????:?comp
??????)
??????const?{?include,?exclude,?max?}?=?props
??????if?(
????????(include?&&?(!name?||?!matches(include,?name)))?||
????????(exclude?&&?name?&&?matches(exclude,?name))
??????)?{
????????current?=?vnode
????????return?rawVNode
??????}
??????const?key?=?vnode.key?==?null???comp?:?vnode.key
??????const?cachedVNode?=?cache.get(key)
??????if?(vnode.el)?{
????????vnode?=?cloneVNode(vnode)
????????if?(rawVNode.shapeFlag?&?ShapeFlags.SUSPENSE)?{
??????????rawVNode.ssContent?=?vnode
????????}
??????}
??????pendingCacheKey?=?key
??????if?(cachedVNode)?{
????????vnode.el?=?cachedVNode.el
????????vnode.component?=?cachedVNode.component
????????if?(vnode.transition)?{
??????????setTransitionHooks(vnode,?vnode.transition!)
????????}
????????vnode.shapeFlag?|=?ShapeFlags.COMPONENT_KEPT_ALIVE
????????keys.delete(key)
????????keys.add(key)
??????}?else?{
????????keys.add(key)
????????if?(max?&&?keys.size?>?parseInt(max?as?string,?10))?{
??????????pruneCacheEntry(keys.values().next().value)
????????}
??????}
??????vnode.shapeFlag?|=?ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
??????current?=?vnode
??????return?rawVNode
????}
??}
主要是對插槽的一些判斷以及對props函數(shù)的處理,最后返回也是需要渲染的vnode
總結(jié)
從源碼中可以看出 keep-alive 就是通過 Map 緩存組件的 vnode 以及會給每個(gè)組件做一個(gè)標(biāo)識,這樣在渲染的時(shí)候就不會再執(zhí)行組件的初始化函數(shù)。
