keep-alive組件的作用與原理分析
什么是keep-alive
“keep-alive” 是 Vue.js 中的一個(gè)特殊組件,用于緩存組件的狀態(tài),以提高應(yīng)用性能。
在 Vue.js 中,組件通常是動(dòng)態(tài)創(chuàng)建和銷(xiāo)毀的,當(dāng)切換到另一個(gè)頁(yè)面或組件時(shí),之前的組件會(huì)被銷(xiāo)毀,再次進(jìn)入時(shí)會(huì)重新創(chuàng)建和初始化。
這樣可能導(dǎo)致組件的狀態(tài)丟失,需要重新初始化,增加了資源的消耗。
組件解決了這個(gè)問(wèn)題,它可以將組件緩存起來(lái),而不是銷(xiāo)毀,使得組件在再次進(jìn)入時(shí)保持之前的狀態(tài),以及避免重復(fù)的創(chuàng)建和初始化過(guò)程。
這樣可以大幅度提高組件的加載速度和性能。
keep-alive的作用
<keep-alive> 的作用是在 Vue.js 應(yīng)用中緩存組件的狀態(tài),以提高應(yīng)用性能和用戶體驗(yàn)。
它可以將組件暫時(shí)保存在內(nèi)存中,而不是每次都重新創(chuàng)建和初始化組件。
主要的作用有以下幾點(diǎn):
- 組件狀態(tài)保持:通過(guò)使用
<keep-alive>,在組件被切換時(shí),其狀態(tài)會(huì)被保留。這意味著組件內(nèi)部的數(shù)據(jù)、狀態(tài)以及一些計(jì)算結(jié)果都會(huì)被緩存,不會(huì)因?yàn)榻M件的銷(xiāo)毀而丟失。當(dāng)再次進(jìn)入該組件時(shí),它會(huì)恢復(fù)到之前的狀態(tài),而不需要重新初始化。這對(duì)于用戶在不同頁(yè)面或組件間切換時(shí)提供了更流暢的體驗(yàn)。 - 減少資源消耗:如果沒(méi)有使用
<keep-alive>,每次切換到一個(gè)組件時(shí),都需要重新創(chuàng)建和初始化組件。對(duì)于復(fù)雜的組件,這可能會(huì)導(dǎo)致不必要的資源消耗,例如重新加載數(shù)據(jù)、執(zhí)行復(fù)雜的計(jì)算等。而使用<keep-alive>,組件被緩存起來(lái),下次再次進(jìn)入時(shí)直接從緩存中恢復(fù),避免了重復(fù)的初始化過(guò)程,大大減少了資源消耗。 - 優(yōu)化性能:由于避免了重復(fù)的創(chuàng)建和初始化過(guò)程,使用
<keep-alive>可以顯著提高組件的加載速度,加快頁(yè)面響應(yīng)時(shí)間,從而提供更好的用戶體驗(yàn)。
需要注意的是,<keep-alive> 并不是適用于所有組件的,特別是對(duì)于一些動(dòng)態(tài)變化的組件,如果希望每次進(jìn)入時(shí)都重新初始化,或者希望釋放組件占用的資源,就不應(yīng)該使用 <keep-alive>。
要使用 <keep-alive>,只需將需要緩存的組件包裹在 <keep-alive> 標(biāo)簽中即可,Vue.js 會(huì)自動(dòng)管理緩存和組件的生命周期。這是一個(gè)簡(jiǎn)單但強(qiáng)大的功能,可在合適的場(chǎng)景下大幅度提升應(yīng)用性能。
原理
<keep-alive> 的原理主要涉及兩個(gè)方面:組件緩存和生命周期的管理。
組件緩存
- 當(dāng)一個(gè)組件被包裹在 <keep-alive> 標(biāo)簽中時(shí),Vue.js 會(huì)將該組件的實(shí)例緩存起來(lái),而不是銷(xiāo)毀它。
- 組件的緩存是通過(guò)一個(gè)名為 cache 的對(duì)象來(lái)管理的,該對(duì)象會(huì)保存被緩存的組件實(shí)例。
- 當(dāng)切換到一個(gè)被緩存的組件時(shí),Vue.js 首先檢查 cache 對(duì)象中是否已經(jīng)有該組件的緩存實(shí)例。如果有,就直接從緩存中取出該實(shí)例;如果沒(méi)有,就創(chuàng)建一個(gè)新的組件實(shí)例并將其緩存起來(lái)。
生命周期的管理
- 在切換到一個(gè)被緩存的組件時(shí),組件的生命周期鉤子函數(shù)并不會(huì)被觸發(fā),而是會(huì)觸發(fā) <keep-alive> 自己的生命周期鉤子函數(shù)。
- <keep-alive> 組件有兩個(gè)主要的生命周期鉤子函數(shù):created 和 destroyed。
- 在組件第一次被緩存時(shí),created 鉤子函數(shù)會(huì)被觸發(fā),表示 <keep-alive> 組件已經(jīng)創(chuàng)建,此時(shí)會(huì)創(chuàng)建被緩存組件的實(shí)例并將其緩存起來(lái)。
- 在切換到其他組件時(shí),destroyed 鉤子函數(shù)會(huì)被觸發(fā),表示 <keep-alive> 組件將被銷(xiāo)毀,此時(shí)會(huì)銷(xiāo)毀所有緩存的組件實(shí)例。
需要注意的是,被包裹在 <keep-alive> 標(biāo)簽中的組件,必須具有唯一的標(biāo)識(shí),否則會(huì)導(dǎo)致緩存沖突。
默認(rèn)情況下,Vue.js 使用組件的名稱作為緩存的標(biāo)識(shí),但也可以通過(guò) key 屬性來(lái)指定唯一的標(biāo)識(shí)。
使用 <keep-alive> 時(shí),要注意以下幾點(diǎn):
- 不是所有組件都適合使用 <keep-alive>,對(duì)于一些動(dòng)態(tài)變化的組件,或者需要每次進(jìn)入時(shí)重新初始化的組件,應(yīng)該避免使用 <keep-alive>。
- 緩存的組件仍然會(huì)觸發(fā) activated 和 deactivated 生命周期鉤子函數(shù),可以在這兩個(gè)鉤子函數(shù)中處理一些特定的邏輯。
- 如果被緩存的組件包含了一些依賴于外部狀態(tài)(如路由參數(shù)、Vuex 狀態(tài)等)的邏輯,需要特別注意在重新進(jìn)入組件時(shí)是否需要重新更新這些狀態(tài)。
總的來(lái)說(shuō),<keep-alive> 提供了一種簡(jiǎn)單且強(qiáng)大的機(jī)制來(lái)優(yōu)化 Vue.js 應(yīng)用的性能,特別是在頻繁切換組件的場(chǎng)景下。
使用
當(dāng)您使用 <keep-alive> 組件時(shí),通常需要將需要緩存的組件包裹在 <keep-alive> 標(biāo)簽中,并為每個(gè)被緩存的組件設(shè)置一個(gè)唯一的 key 屬性,以確保緩存的正確性。
下面是一個(gè)使用 <keep-alive> 組件的示例:
假設(shè)我們有兩個(gè)組件,一個(gè)是用于顯示用戶信息的組件 <UserProfile>,另一個(gè)是用于顯示用戶訂單信息的組件 <UserOrders>。
我們希望在用戶切換到 <UserProfile> 組件時(shí),保持該組件的狀態(tài),并且在用戶切換到 <UserOrders> 組件后再切換回來(lái)時(shí),不重新初始化 <UserProfile> 組件。
<template>
<div>
<keep-alive>
<!-- 使用 key 屬性確保組件的正確緩存 -->
<component :is="currentComponent" :key="currentComponent" />
</keep-alive>
<button @click="showUserProfile">Show User Profile</button>
<button @click="showUserOrders">Show User Orders</button>
</div>
</template>
<script>
import UserProfile from './UserProfile.vue';
import UserOrders from './UserOrders.vue';
export default {
components: {
UserProfile,
UserOrders,
},
data() {
return {
currentComponent: 'UserProfile', // 初始顯示用戶信息組件
};
},
methods: {
showUserProfile() {
this.currentComponent = 'UserProfile';
},
showUserOrders() {
this.currentComponent = 'UserOrders';
},
},
};
</script>
在上面的示例中,使用了動(dòng)態(tài)組件 <component :is="currentComponent"> 來(lái)動(dòng)態(tài)地切換顯示 <UserProfile> 和 <UserOrders> 組件。
同時(shí),將 <keep-alive> 標(biāo)簽包裹在動(dòng)態(tài)組件外部,這樣 <keep-alive> 會(huì)緩存當(dāng)前被顯示的組件。
在切換組件時(shí),使用 key 屬性來(lái)確保緩存的正確性。當(dāng)切換到不同的組件時(shí),key 的值會(huì)變化,這會(huì)觸發(fā) <keep-alive> 的重新緩存行為。
注意,key 屬性應(yīng)該是唯一的,以確保每個(gè)組件都能被正確地緩存。在實(shí)際應(yīng)用中,可能需要根據(jù)組件的具體情況設(shè)置不同的 key 值。
理解源碼
<keep-alive> 組件的源碼相對(duì)比較復(fù)雜,涉及到 Vue.js 的虛擬 DOM、組件實(shí)例管理、生命周期管理等方面。
下面簡(jiǎn)要介紹 <keep-alive> 的關(guān)鍵源碼部分,以便了解其基本原理。
在 Vue.js 的源碼中,<keep-alive> 組件是由一個(gè)特殊的內(nèi)置組件 KeepAlive 實(shí)現(xiàn)的。它的主要作用是處理組件的緩存和管理緩存組件的生命周期。
組件的緩存實(shí)現(xiàn)
KeepAlive組件內(nèi)部維護(hù)了一個(gè)名為cache的對(duì)象,用于存儲(chǔ)緩存的組件實(shí)例。- 在切換到一個(gè)被緩存的組件時(shí),
KeepAlive組件首先會(huì)檢查cache對(duì)象,是否已經(jīng)有該組件的緩存實(shí)例。 - 如果緩存中有該組件實(shí)例,
KeepAlive直接返回緩存的組件實(shí)例;如果沒(méi)有,KeepAlive會(huì)創(chuàng)建一個(gè)新的組件實(shí)例,并將其緩存起來(lái)。
組件生命周期的管理
KeepAlive組件有兩個(gè)重要的生命周期鉤子函數(shù):created和destroyed。- 在
created鉤子函數(shù)中,KeepAlive會(huì)監(jiān)聽(tīng)父組件的include和exclude屬性的變化,以決定是否緩存某個(gè)組件。 - 在切換到被緩存組件時(shí),
KeepAlive會(huì)觸發(fā)activated生命周期鉤子函數(shù),并從cache中取出對(duì)應(yīng)的緩存組件實(shí)例。如果沒(méi)有緩存實(shí)例,會(huì)觸發(fā)被緩存組件的created生命周期。 - 在切換到其他組件時(shí),
KeepAlive會(huì)觸發(fā)deactivated生命周期鉤子函數(shù),并將當(dāng)前緩存的組件實(shí)例暫時(shí)從cache中移除。如果需要緩存,則緩存的組件實(shí)例并不會(huì)被銷(xiāo)毀。
組件銷(xiāo)毀時(shí)的處理
- 在
destroyed鉤子函數(shù)中,KeepAlive會(huì)銷(xiāo)毀所有緩存的組件實(shí)例,并清空cache對(duì)象。
如果想深入了解 <keep-alive> 的源碼實(shí)現(xiàn),可以查閱 Vue.js 的 GitHub 倉(cāng)庫(kù)并瀏覽相關(guān)代碼 keep-alive源碼。
這個(gè)是從github拿的源碼,有興趣可以研究一下。
import { isRegExp, isArray, remove } from 'shared/util'
import { getFirstComponentChild } from 'core/vdom/helpers/index'
import type VNode from 'core/vdom/vnode'
import type { VNodeComponentOptions } from 'types/vnode'
import type { Component } from 'types/component'
import { getComponentName } from '../vdom/create-component'
type CacheEntry = {
name?: string
tag?: string
componentInstance?: Component
}
type CacheEntryMap = Record<string, CacheEntry | null>
function _getComponentName(opts?: VNodeComponentOptions): string | null {
return opts && (getComponentName(opts.Ctor.options as any) || opts.tag)
}
function matches(
pattern: string | RegExp | Array<string>,
name: string
): boolean {
if (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
}
function pruneCache(
keepAliveInstance: { cache: CacheEntryMap; keys: string[]; _vnode: VNode },
filter: Function
) {
const { cache, keys, _vnode } = keepAliveInstance
for (const key in cache) {
const entry = cache[key]
if (entry) {
const name = entry.name
if (name && !filter(name)) {
pruneCacheEntry(cache, key, keys, _vnode)
}
}
}
}
function pruneCacheEntry(
cache: CacheEntryMap,
key: string,
keys: Array<string>,
current?: VNode
) {
const entry = cache[key]
if (entry && (!current || entry.tag !== current.tag)) {
// @ts-expect-error can be undefined
entry.componentInstance.$destroy()
}
cache[key] = null
remove(keys, key)
}
const patternTypes: Array<Function> = [String, RegExp, Array]
// TODO defineComponent
export default {
name: 'keep-alive',
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
methods: {
cacheVNode() {
const { cache, keys, vnodeToCache, keyToCache } = this
if (vnodeToCache) {
const { tag, componentInstance, componentOptions } = vnodeToCache
cache[keyToCache] = {
name: _getComponentName(componentOptions),
tag,
componentInstance
}
keys.push(keyToCache)
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
this.vnodeToCache = null
}
}
},
created() {
this.cache = Object.create(null)
this.keys = []
},
destroyed() {
for (const key in this.cache) {
pruneCacheEntry(this.cache, key, this.keys)
}
},
mounted() {
this.cacheVNode()
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
updated() {
this.cacheVNode()
},
render() {
const slot = this.$slots.default
const vnode = getFirstComponentChild(slot)
const componentOptions = vnode && vnode.componentOptions
if (componentOptions) {
// check pattern
const name = _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 =
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 {
// delay setting the cache until update
this.vnodeToCache = vnode
this.keyToCache = key
}
// @ts-expect-error can vnode.data can be undefined
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Vue filter格式化時(shí)間戳?xí)r間成標(biāo)準(zhǔn)日期格式的方法
今天小編就為大家分享一篇Vue filter格式化時(shí)間戳?xí)r間成標(biāo)準(zhǔn)日期格式的方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-09-09
django+vue項(xiàng)目搭建實(shí)現(xiàn)前后端通信
本文主要介紹了django+vue項(xiàng)目搭建實(shí)現(xiàn)前后端通信,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02
vue?實(shí)現(xiàn)彈窗關(guān)閉后刷新效果
這篇文章主要介紹了vue?實(shí)現(xiàn)彈窗關(guān)閉后刷新效果,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-04-04
vue cli 3.0通用打包配置代碼,不分一二級(jí)目錄
這篇文章主要介紹了vue cli 3.0通用打包配置代碼,不分一二級(jí)目錄,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-09-09
基于vue監(jiān)聽(tīng)滾動(dòng)事件實(shí)現(xiàn)錨點(diǎn)鏈接平滑滾動(dòng)的方法
本篇文章主要介紹了基于vue監(jiān)聽(tīng)滾動(dòng)事件實(shí)現(xiàn)錨點(diǎn)鏈接平滑滾動(dòng)的方法,非常具有實(shí)用價(jià)值,有興趣的可以了解一下2018-01-01

