<!-- 基本 -->
<keep-alive>
<component :is="view"></component>
</keep-alive>
<!-- 多個條件判斷的子組件 -->
<keep-alive>
<comp-a v-if="a > 1"></comp-a>
<comp-b v-else></comp-b>
</keep-alive>
<!-- 常見應用 -->
<keep-alive>
<router-view></router-view>
</keep-alive>
複製代碼
keep-alive 要求同時只有一個子元素被渲染。javascript
include
-- 逗號分隔字符串、正則表達式或一個數組。只有名稱匹配的組件會被緩存。
exclude
-- 逗號分隔字符串、正則表達式或一個數組。任何名稱匹配的組件都不會被緩存。
max
-- 最多能夠緩存多少組件實例。java
keep-alive
提供了兩個生命鉤子,分別是activated
與deactivated
。
由於keep-alive
會將組件保存在內存中,並不會銷燬以及從新建立,因此不會從新調用組件的created
等方法,須要用activated
與deactivated
這兩個生命鉤子來得知當前組件是否處於活動狀態。node
咱們從源碼角度看一下keep-alive
是如何實現組件的緩存的。正則表達式
const patternTypes: Array<Function> = [String, RegExp, Array]
export default {
name: 'keep-alive',
// 表示是抽象組件
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created () {
// 用於緩存vnode對象
this.cache = Object.create(null)
this.keys = []
},
// destroyed鉤子中銷燬全部cache中的組件實例
destroyed () {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
// 監聽include和exclude屬性
mounted () {
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
render () {
// ...省略代碼(render函數後面詳細講解)
return vnode || (slot && slot[0])
}
}
複製代碼
首先看一下created
鉤子中,緩存一個cache
對象,用來緩存vnode
節點。同時還緩存了一個keys
數組,緩存的vnode
節點的惟一標識。數組
destroyed
鉤子在組件被銷燬的時候清除cache
緩存中的全部組件實例。下面是銷燬組件的函數實現:1.把緩存中對應key
的vnode
的組件實例destroy
;2.從cache
中移除,並在keys
數組中移除對應的key
。緩存
function pruneCacheEntry (cache, key, keys, current) {
const cached = cache[key]
if (cached && (!current || cached.tag !== current.tag)) {
// 判斷cache的vnode不是目前渲染的vnode,則銷燬
cached.componentInstance.$destroy()
}
cache[key] = null
remove(keys, key)
}
複製代碼
接下來看一下組件中render函數的實現:作了詳細註釋函數
render () {
// 獲取默認插槽
const slot = this.$slots.default
// 獲取第一個子組件(注意keepalive組件同時只能渲染一個子元素)
const vnode: VNode = getFirstComponentChild(slot)
// componentOptions中存儲了組件的配置項
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// 獲取到組件的name,存在組件名則直接使用組件名,不然會使用tag
const name: ?string = getComponentName(componentOptions)
const { include, exclude } = this
if (
(include && (!name || !matches(include, name))) ||
(exclude && name && matches(exclude, name))
) {
// 若是配置了include可是組件名不匹配include 或者 配置了exclude且組件名匹配exclude,那麼就直接返回這個組件的vnode
return vnode
}
const { cache, keys } = this
// 獲取或生成表明組件的惟一標識key
const key: ?string = vnode.key == null
? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
if (cache[key]) {
// cache緩存中存在這個vnode,那麼直接將緩存的vnode的componentInstance(組件實例)覆蓋到目前的vnode上面
vnode.componentInstance = cache[key].componentInstance
// 更新keys數組,讓最新渲染的vnodekey推到尾部
remove(keys, key)
keys.push(key)
} else {
// 若是沒有緩存過,則將vnode緩存到cache中
cache[key] = vnode
keys.push(key)
// 若是緩存的組件實例數量大於max時,銷燬最先緩存的那個組件
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
// keepAlive標記位
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
複製代碼
首先獲取到它的默認插槽,而後再獲取到它的第一個子組件,獲取該組件的name
(存在組件名則直接使用組件名,不然會使用tag
)。接下來會將這個name
經過include
與exclude
屬性進行匹配,匹配不成功(說明不須要進行緩存)則不進行任何操做直接返回vnode
。ui
檢測include
與exclude
屬性匹配的函數很簡單:this
// 判斷name是否匹配pattern
function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
if (Array.isArray(pattern)) {
return pattern.indexOf(name) > -1
} else if (typeof pattern === 'string') {
return pattern.split(',').indexOf(name) > -1
} else if (isRegExp(pattern)) {
return pattern.test(name)
}
/* istanbul ignore next */
return false
}
複製代碼
include
與exclude
屬性支持數組、支持字符串如"a,b,c"這樣組件名以逗號隔開的狀況以及正則表達式。matches
經過這三種方式分別檢測是否匹配當前組件。spa
接着,根據key
在this.cache
中查找,若是存在則說明以前已經緩存過了,直接將緩存的vnode
的componentInstance
(組件實例)覆蓋到目前的vnode
上面。不然將vnode存儲在cache中。
最後返回vnode
(有緩存時該vnode
的componentInstance
已經被替換成緩存中的了)。
咱們注意到mounted鉤子中,咱們對exclude屬性和include屬性作了監聽。
mounted () {
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
}
複製代碼
也就是說,咱們會監聽這兩個屬性的變化,改變的時候修改cache緩存中的緩存數據。其實會對cache
作遍歷,發現緩存的節點名稱和新的規則沒有匹配上的時候,就把這個緩存節點從緩存中摘除。咱們看看pruneCache
函數的實現:
function pruneCache (keepAliveInstance: any, filter: Function) {
// _vnode:表示目前組件的渲染節點
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)
}
}
}
}
複製代碼
遍歷cache
緩存,name
不符合filter
條件的時候則調用pruneCacheEntry
方法銷燬vnode
對應的組件實例(Vue實例),並從cache
中移除。
keep-alive
組件是一個抽象組件,而且它的緩存是基於VNode節點的緩存,它的實現是經過自定義render函數而且利用了插槽。並且會隨時監聽include和exclude屬性的變化作緩存數據的變化。