keep-alive
這個功能是vue特有的挠乳,在react中蛾茉,我暫時沒有找過相關組件讼呢,所以第一次用這個標簽時,覺得特別神奇谦炬,跳轉路由竟然會保存視圖狀態(tài)悦屏。
keep-alive是Vue.js的一個內置組件节沦。它能夠不活動的組件實例保存在內存中,而不是直接將其銷毀础爬,它是一個抽象組件甫贯,不會被渲染到真實DOM中,也不會出現(xiàn)在父組件鏈中看蚜。
它提供了include與exclude兩個屬性叫搁,允許組件有條件地進行緩存。
具體內容可以參考官網(wǎng)供炎。
用法
<keep-alive>
<component></component>
</keep-alive>
這里的component組件會被緩存起來渴逻。
舉個例子
<keep-alive>
<coma v-if="test"></coma>
<comb v-else></comb>
</keep-alive>
<button @click="test=handleClick">請點擊</button>
export default {
data () {
return {
test: true
}
},
methods: {
handleClick () {
this.test = !this.test;
}
}
}
在點擊button時候,組件會進行切換音诫,但是這個時候兩個組件的狀態(tài)都能被保存起來惨奕,步如coma與comb組件都有一個input標簽,那么input標簽的內容是不會因組件切換而消失的竭钝。
props
keep-alive組件提供了include與exclude兩個屬性來允許組件有條件地進行緩存梨撞,二者都可以用逗號分隔字符串、正則表達式或一個數(shù)組來表示香罐。
生命鉤子
keep-alive提供了兩個生命鉤子卧波,分別是activated與deactivated。
因為keep-alive會將組件保存在內存中穴吹,并不會銷毀以及重新創(chuàng)建幽勒,所以不會重新調用組件的created等方法,需要用activated與deactivated這兩個生命鉤子來得知當前組件是否處于活動狀態(tài)港令。
深入keep-alive組件實現(xiàn)
說完了keep-alve組件的使用啥容,我們從源碼角度看一下keep-alive組件是如何實現(xiàn)組件的緩存的
created 與 destroyed鉤子
created鉤子會創(chuàng)建一個cache對象,用于作為緩存容器顷霹,保存vnode節(jié)點
created () {
/* 緩存對象 */
this.cache = Object.create(null)
}
destroyed鉤子則在組件被銷毀的時候清除cache緩存中的所有組件實例咪惠。
/* destroyed鉤子中銷毀所有cache中的組件實例 */
destroyed () {
for (const key in this.cache) {
pruneCacheEntry(this.cache[key])
}
},
render
接下來是render函數(shù)。
render() {
// 得到slot插槽的第一個組件
const vnode : VNode = getFirstComponentChild(this.$slots.default)
// ts寫法 componentOptions默認值為vnode.componentOptions 且不能為空
const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
if(componentOptions) {
// 獲取組件名稱
const name ?:string = getComponentName(componentOptions)
// name不在include中或者在exclude中淋淀,則直接返回vnode(沒有取緩存)
if(name && (
(this.include && !matches(this.include,name)) ||
(this.exclude && matches(this.exclude,name))
)) {
return vnode
}
const key : ?string = vnode.key == null ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
: vnode.key
// 如果已經(jīng)做過緩存則直接從緩存中獲取組件實例給vnode遥昧,未緩存過的則進行緩存
if(this.cache[key]) {
vnode.componentInstance = this.cache[key].componentInstance
} else {
// 沒有緩存,進行緩存
this.cache[key] = vnode
}
}
return vnode
}
這個cache居然使用set來存儲朵纷,正常來講不是使用hashtable更好么炭臭?
/* 檢測name是否匹配 */
function matches (pattern: string | RegExp, name: string): boolean {
if (typeof pattern === 'string') {
/* 字符串情況,如a,b,c */
return pattern.split(',').indexOf(name) > -1
} else if (isRegExp(pattern)) {
/* 正則 */
return pattern.test(name)
}
/* istanbul ignore next */
return false
}
檢測include與exclude屬性匹配的函數(shù)很簡單袍辞,include與exclude屬性支持字符串如"a,b,c"這樣組件名以逗號隔開的情況以及正則表達式鞋仍。matches通過這兩種方式分別檢測是否匹配當前組件。
接下來的事情很簡單搅吁,根據(jù)key在this.cache中查找威创,如果存在則說明之前已經(jīng)緩存過了落午,直接將緩存的vnode的componentInstance(組件實例)覆蓋到目前的vnode上面。否則將vnode存儲在cache中肚豺。
最后返回vnode(有緩存時該vnode的componentInstance已經(jīng)被替換成緩存中的了)溃斋。
watch
用watch來監(jiān)聽pruneCache與pruneCache這兩個屬性的改變,在改變的時候修改cache緩存中的緩存數(shù)據(jù)吸申。
watch: {
/* 監(jiān)視include以及exclude梗劫,在被修改的時候對cache進行修正 */
include (val: string | RegExp) {
pruneCache(this.cache, this._vnode, name => matches(val, name))
},
exclude (val: string | RegExp) {
pruneCache(this.cache, this._vnode, name => !matches(val, name))
}
},
讓我們來看看修正函數(shù)pruneCache的實現(xiàn)。
/**
* @param {VNodeCache} cache 緩存列表
* @param {VNode} 當前keep-alive下的視圖節(jié)點
* @param {function 過濾函數(shù)
*/
function pruneCache(cache:VNodeCache,current:VNode,,filter:Function) {
for(const key in cache) {
// 取出cache中的vnode
const cacheNode = ?VNode = cache[key]
if(cachedNode) {
const name: ?string = getComponentName(cachedNode.componentOptions)
// 如果該緩存組件不在緩存名單了呛谜,則進入暗殺名單
if(name && !filter(name)) {
// 不是當前視圖的都干掉
if(cacheNode !== current) {
pruneCacheEntry(cachedNode)
}
// 刪除緩存集合中的對象
cache[key] = null
}
}
}
}
/* 銷毀vnode對應的組件實例(Vue實例) */
function pruneCacheEntry (vnode: ?VNode) {
if (vnode) {
vnode.componentInstance.$destroy()
}
}
遍歷cache中的所有項在跳,如果不符合filter指定的規(guī)則的話,則會執(zhí)行pruneCacheEntry隐岛。pruneCacheEntry則會調用組件實例的$destroy方法來將組件銷毀猫妙。
最后
keep-alive組件的緩存也是基于VNode節(jié)點的而不是直接存儲DOM結構。它將滿足條件(pruneCache與pruneCache)的組件在cache對象中緩存起來聚凹,在需要重新渲染的時候再將vnode節(jié)點從cache對象中取出并渲染割坠。