keep-alive保持組件狀態(tài)的方法
keep-alive的設(shè)計(jì)初衷
有些業(yè)務(wù)場(chǎng)景需要根據(jù)不同的判斷條件,動(dòng)態(tài)地在多個(gè)組件之間切換。頻繁的組件切換會(huì)導(dǎo)致組件反復(fù)渲染,如果組件包含有大量的邏輯和dom節(jié)點(diǎn),極易造成性能問(wèn)題。其次,切換后組件的狀態(tài)也會(huì)完全丟失。keep-alive的設(shè)計(jì)初衷就是為了保持組件的狀態(tài),避免組件的重復(fù)渲染。
為什么keep-alive可以直接使用
開發(fā)者無(wú)需注冊(cè)和引入,直接可以在模板中使用。 跟開發(fā)者使用Vue.component自定義的組件不同,keep-alive無(wú)需注冊(cè),在模板中直接可以使用,如下所示:
<keep-alive> <component :is="view"></component> </keep-alive>
這是因?yàn)閗eep-alive是vue的內(nèi)置組件,已經(jīng)在vue中提前定義。
// core/components/keep-alive.js
export default {
name: 'keep-alive',
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created () {
this.cache = Object.create(null)
this.keys = []
},
destroyed () {
// keep-alive的銷毀,將所有緩存的組件清除
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted () {
// 如果指定了include和exclude屬性,需要實(shí)時(shí)觀察當(dāng)前這兩個(gè)屬性的變化,以及時(shí)的更新緩存
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
render () {
// keepAlive組件本身不會(huì)被渲染成dom節(jié)點(diǎn),其render方法的處理邏輯的是將其包裹的組件的vnode返回
const slot = this.$slots.default
// 獲取第一個(gè)組件子節(jié)點(diǎn)
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
// 1、如果緩存中存在該vnode,從緩存中取得該組件的實(shí)例(一個(gè)組件對(duì)應(yīng)一顆vnode樹,同時(shí)一個(gè)組件對(duì)應(yīng)一個(gè)vue子類的實(shí)例),不再重新創(chuàng)建
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
// 將當(dāng)前的組件的key作為最新的緩存(更新其在keys數(shù)組中的順序)
remove(keys, key)
keys.push(key)
} else {
// 2、如果未命中緩存,添加到緩存
cache[key] = vnode
keys.push(key)
// 如果緩存超過(guò)限制,淘汰最舊的緩存
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
// 標(biāo)記為keepAlive組件
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
這是因?yàn)閗eep-alive是vue的內(nèi)置組件,已經(jīng)在vue中提前定義。
// core/components/keep-alive.js
export default {
name: 'keep-alive',
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created () {
this.cache = Object.create(null)
this.keys = []
},
destroyed () {
// keep-alive的銷毀,將所有緩存的組件清除
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted () {
// 如果指定了include和exclude屬性,需要實(shí)時(shí)觀察當(dāng)前這兩個(gè)屬性的變化,以及時(shí)的更新緩存
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
render () {
// keepAlive組件本身不會(huì)被渲染成dom節(jié)點(diǎn),其render方法的處理邏輯的是將其包裹的組件的vnode返回
const slot = this.$slots.default
// 獲取第一個(gè)組件子節(jié)點(diǎn)
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
// 1、如果緩存中存在該vnode,從緩存中取得該組件的實(shí)例(一個(gè)組件對(duì)應(yīng)一顆vnode樹,同時(shí)一個(gè)組件對(duì)應(yīng)一個(gè)vue子類的實(shí)例),不再重新創(chuàng)建
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
// 將當(dāng)前的組件的key作為最新的緩存(更新其在keys數(shù)組中的順序)
remove(keys, key)
keys.push(key)
} else {
// 2、如果未命中緩存,添加到緩存
cache[key] = vnode
keys.push(key)
// 如果緩存超過(guò)限制,淘汰最舊的緩存
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
// 標(biāo)記為keepAlive組件
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
// core/components/index.js
import KeepAlive from './keep-alive'
export default {
KeepAlive
}
// core/global-api/index.js
// 導(dǎo)入內(nèi)置組件
import builtInComponents from '../components/index'
/**
* 為Vue添加全局方法和屬性
* @param {GlobalAPI} Vue
*/
export function initGlobalAPI (Vue: GlobalAPI) {
// ...省略了無(wú)關(guān)代碼
Vue.options = Object.create(null)
// 添加內(nèi)置組件keep-alive
extend(Vue.options.components, builtInComponents)
}
buildInComponents中包含了keep-alive的定義。在initGlobalAPI方法中,將內(nèi)置組件添加到了 vue的全局變量中。
extend(A, B)是個(gè)簡(jiǎn)單的對(duì)象屬性復(fù)制方法。將對(duì)象B中的屬性復(fù)制到對(duì)象A中。
keep-alive是如何保持組件狀態(tài)的
為了保持組件狀態(tài),keep-alive設(shè)計(jì)了緩存機(jī)制。
我們知道,模板中的每個(gè)HTML標(biāo)簽在vue中由相應(yīng)的vnode節(jié)點(diǎn)對(duì)象來(lái)表示。如果是HTML標(biāo)簽是組件標(biāo)簽,需要為該標(biāo)簽的vnode創(chuàng)建一個(gè)組件實(shí)例。組件實(shí)例負(fù)責(zé)組件內(nèi)的HTML模板的編譯和渲染。因此相比于普通HTML標(biāo)簽的vnode節(jié)點(diǎn),組件vnode節(jié)點(diǎn)會(huì)存在componentOptions和 componentInstance 這兩個(gè)屬性中保存組件選項(xiàng)對(duì)象和組件實(shí)例的引用。
首先,我們從keep-alive組件的實(shí)現(xiàn)代碼中可以看到在組件的created鉤子中設(shè)計(jì)了緩存機(jī)制:
created () {
this.cache = Object.create(null)
this.keys = []
}
keep-alive設(shè)置了cache和keys兩個(gè)屬性來(lái)緩存子組件。其中cache中的每項(xiàng)是一個(gè)以所包裹的組件的組件名為key,包裹組件對(duì)應(yīng)的vnoded為值的對(duì)象。keys的每一項(xiàng)是其所包裹的組件的組件名。
render 函數(shù)是vue實(shí)例和vue組件實(shí)例中用來(lái)創(chuàng)建vnode的方法。我們?cè)趯?shí)際的應(yīng)用中,一般是通過(guò)template和el來(lái)指定模板,然后由vue將模板編譯成render函數(shù)。如果用戶希望能更靈活地控制vnode的創(chuàng)建可以提供自定義的render函數(shù)。
render () {
const slot = this.$slots.default
// 獲取第一個(gè)組件子節(jié)點(diǎn)
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
// 1、如果緩存中存在該vnode,從緩存中取得該組件的實(shí)例(一個(gè)組件對(duì)應(yīng)一顆vnode樹,同時(shí)一個(gè)組件對(duì)應(yīng)一個(gè)vue子類的實(shí)例),不再重新創(chuàng)建
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
// 將當(dāng)前的組件的key作為最新的緩存(更新其在keys數(shù)組中的順序)
remove(keys, key)
keys.push(key)
} else {
// 2、如果未命中緩存,添加到緩存
cache[key] = vnode
keys.push(key)
// 如果緩存超過(guò)限制,淘汰最舊的緩存
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
// 標(biāo)記為keepAlive組件
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
keep-alive內(nèi)部就是單獨(dú)提供了render函數(shù)來(lái)自定義了vnode的創(chuàng)建邏輯。首先keep-alive獲取到其所包裹的子組件的根vnode,然后去cache中查找該組件是否存在。
如果cache中不存在子組件vnode,則以{子組件名: 子組件vnode}的形式保存到cache對(duì)象中。同時(shí)將子組件名字保存到keys數(shù)組中。同時(shí)如果當(dāng)前緩存的數(shù)量已經(jīng)超過(guò)max所設(shè)置的最大值,需要淘汰掉最近最少使用的緩存項(xiàng)(LRU)。
如果cache中存在子組件vnode,那么只需要復(fù)用緩存的組件vnode的組件實(shí)例(componentInstance)。同時(shí)需要將該子組件vnode在緩存中順序調(diào)到最前面,這個(gè)主要是為了在緩存不足時(shí),正確地淘汰緩存項(xiàng)。
舉例說(shuō)明
最后通過(guò)一個(gè)例子加深一下理解。
<div id="app"> <keep-alive><component :is="view"></component></keep-alive> <button @click="view = view =='count'? 'any': 'count'">切換組件</button> </div>
Vue.component("count", {
data() {
return {
count:0
};
},
template: "<div @click='count+=1'>點(diǎn)了我 {{count}} 次</div>"
});
Vue.component("any", {
template: "<div>any</div>"
});
new Vue({
el: "#app",
data: {
view: "count"
}
});
由于view默認(rèn)值是count,因此keep-alive包裹的子組件是count。此時(shí)keep-alive的緩存中為空,因此會(huì)把組件count的vnode添加到緩存。緩存結(jié)果為
cache = {1::count: {tag: "vue-component-1-count", data:{tag: "component", hook: {…}}}, componentOptions, componentInstance, ...}
keys = ["1::count"]

點(diǎn)擊一下組件count,組件的顯示內(nèi)容變成"點(diǎn)了我1次",然后切換到組件any。與count組件相同,由于在keep-alive的緩存中還未保存any組件的vnode,因此需要將any添加到緩存中。此時(shí)緩存結(jié)果變成了:
cache = {
1::count: {tag: "vue-component-1-count", data:{tag: "component", hook: {…}}, componentOptions, componentInstance, ...},
2::any: {tag: "vue-component-2-any", data:{tag: "component", hook: {…}}, componentOptions, componentInstance, ...},
}
keys = ["1::count", "2::any"]
頁(yè)面顯示結(jié)果為:

再次點(diǎn)擊切換組件,切回count。此時(shí)count組件的vnode在緩存中已經(jīng)存在,所以直接復(fù)用了原來(lái)count組件vnode中所保存的組件實(shí)例,組件實(shí)例中保存了原來(lái)的count值,因此組件切換完后,組件的狀態(tài)也跟著還原了回來(lái)。
下圖為count組件實(shí)例的狀態(tài),可以看到count的值得到了保持:

最終頁(yè)面顯示結(jié)果為:

從上面的分析可知,如果組件被包裹在keep-alive組件中,組件vnode會(huì)緩存到cache中。而組件的狀態(tài)又會(huì)保存在組件實(shí)例中(componentInstance),當(dāng)組件再次切換回來(lái)時(shí),keep-alive直接將之前緩存的狀態(tài)進(jìn)行還原,也就實(shí)現(xiàn)了組件狀態(tài)的保持。
以上就是keep-alive保持組件狀態(tài)的方法的詳細(xì)內(nèi)容,更多關(guān)于keep-alive保持組件狀態(tài)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- vue keep-alive實(shí)現(xiàn)多組件嵌套中個(gè)別組件存活不銷毀的操作
- vue使用keep-alive實(shí)現(xiàn)組件切換時(shí)保存原組件數(shù)據(jù)方法
- vue內(nèi)置組件keep-alive事件動(dòng)態(tài)緩存實(shí)例
- Vue中keep-alive組件的深入理解
- vue中keep-alive內(nèi)置組件緩存的實(shí)例代碼
- Vue中keep-alive組件作用詳解
- vue keep-alive 動(dòng)態(tài)刪除組件緩存的例子
- vue組件 keep-alive 和 transition 使用詳解
- vue中keep-alive組件的入門使用教程
- vue里如何主動(dòng)銷毀keep-alive緩存的組件
- vue 使某個(gè)組件不被 keep-alive 緩存的方法
- vue.js內(nèi)置組件之keep-alive組件使用
相關(guān)文章
vue中filters 傳入兩個(gè)參數(shù) / 使用兩個(gè)filters的實(shí)現(xiàn)方法
這篇文章主要介紹了vue中filters 傳入兩個(gè)參數(shù) / 使用兩個(gè)filters的實(shí)現(xiàn)方法,文中給大家提到了Vue 中的 filter 帶多參的使用方法,需要的朋友可以參考下2019-07-07
laravel5.4+vue+element簡(jiǎn)單搭建的示例代碼
本篇文章主要介紹了laravel5.4+vue+element簡(jiǎn)單搭建的示例代碼,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08
有關(guān)vue 組件切換,動(dòng)態(tài)組件,組件緩存
這篇文章主要介紹了有關(guān)vue 組件切換,動(dòng)態(tài)組件,組件緩存,在組件化開發(fā)模式下,我們會(huì)把整個(gè)項(xiàng)目拆分成很多組件,然后按照合理的方式組織起來(lái),達(dá)到預(yù)期效果,下面來(lái)看看文章的詳細(xì)內(nèi)容2021-11-11
VUE項(xiàng)目啟動(dòng)沒(méi)有問(wèn)題但代碼中script標(biāo)簽有藍(lán)色波浪線標(biāo)注
這篇文章主要給大家介紹了關(guān)于VUE項(xiàng)目啟動(dòng)沒(méi)有問(wèn)題但代碼中script標(biāo)簽有藍(lán)色波浪線標(biāo)注的相關(guān)資料,文中將遇到的問(wèn)題以及解決的方法介紹的非常詳細(xì),需要的朋友可以參考下2023-05-05
Vue實(shí)現(xiàn)base64編碼圖片間的切換功能
這篇文章主要介紹了Vue實(shí)現(xiàn)base64編碼圖片間的切換功能,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-12-12
vue3?使用setup語(yǔ)法糖實(shí)現(xiàn)分類管理功能
這篇文章主要介紹了vue3?使用setup語(yǔ)法糖實(shí)現(xiàn)分類管理,本次模塊使用 vue3+element-plus 實(shí)現(xiàn)一個(gè)新聞?wù)镜暮笈_(tái)分類管理模塊,其中新增、編輯采用對(duì)話框方式公用一個(gè)表單,需要的朋友可以參考下2022-08-08

