Vue中keep-alive元件的理解
阿新 • • 發佈:2020-08-23
# 對keep-alive元件的理解
當在元件之間切換的時候,有時會想保持這些元件的狀態,以避免反覆重渲染導致的效能等問題,使用``包裹動態元件時,會快取不活動的元件例項,而不是銷燬它們。
## 描述
重新建立動態元件的行為通常是非常有用的,但是在有些情況下我們更希望那些標籤的元件例項能夠被在它們第一次被建立的時候快取下來,此時使用``包裹元件即可快取當前元件例項,將元件快取到記憶體,用於保留元件狀態或避免重新渲染,和``相似它,其自身不會渲染一個`DOM`元素,也不會出現在元件的父元件鏈中。
```html
```
被``包含的元件不會被再次初始化,也就意味著不會重走生命週期函式,``保持了當前的元件的狀態,在第一次建立的時候回正常觸發其建立生命週期,但是由於元件其實並未銷燬,所以不會觸發元件的銷燬生命週期,而當元件在``內被切換時,它的`activated`和`deactivated`這兩個生命週期鉤子函式將會被對應執行。
```javascript
export default {
data: function() {
return {
}
},
activated: function(){
console.log("activated");
},
deactivated: function(){
console.log("deactivated");
},
}
```
``可以接收`3`個屬性做為引數進行匹配對應的元件進行快取,匹配首先檢查元件自身的`name`選項,如果`name`選項不可用,則匹配它的區域性註冊名稱,即父元件`components`選項的鍵值,匿名元件不能被匹配,除了使用``的`props`控制組件快取,通常還可以配合`vue-router`在定義時的`meta`屬性以及在`template`定義的``進行元件的有條件的快取控制。
* `include`: 包含的元件,可以為字串,陣列,以及正則表示式,只有匹配的元件會被快取。
* `exclude`: 排除的元件,以為字串,陣列,以及正則表示式,任何匹配的元件都不會被快取,當匹配條件同時在`include`與`exclude`存在時,以`exclude`優先順序最高。
* `max`: 快取元件的最大值,型別為字元或者數字,可以控制快取元件的個數,一旦這個數字達到了,在新例項被建立之前,已快取元件中最久沒有被訪問的例項會被銷燬掉。
```html
```
``是用在其一個直屬的子元件被開關的情形,如果在其中有`v-for`則不會工作,如果有上述的多個條件性的子元素,``要求同時只有一個子元素被渲染,通俗點說,``最多同時只能存在一個子元件,在``的`render`函式中定義的是在渲染``內的元件時,`Vue`是取其第一個直屬子元件來進行快取。
```javascript
const vnode: VNode = getFirstComponentChild(this.$slots.default);
```
## 實現
`Vue`中``元件原始碼定義在`dev/src/core/components/keep-alive.js`,本次分析實現的`commit id`為`215f877`。
在``初始化時的`created`階段會初始化兩個變數,分別為`cache`和`keys`,`mounted`階段會對`include`和`exclude`變數的值做監測。
```javascript
export default {
created () {
this.cache = Object.create(null)
this.keys = []
},
mounted () {
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
},
}
```
上邊的`$watch`方法能夠對引數的變化進行檢測,如果`include`或者`exclude`的值發生變化,就會觸發`pruneCache`函式,不過篩選的條件需要根據`matches`函式的返回值來決定,`matches`函式接收三種類型的引數`string`、`RegExp`、`Array`,用以決定是否進行快取。
```javascript
function matches (pattern: string | RegExp | Array, 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
}
```
`pruneCache`函式用以修建不符合條件的`key`值,每當過濾條件改變,都需要呼叫`pruneCacheEntry`方法從已有的快取中修建不符合條件的`key`。
```javascript
function pruneCache (keepAliveInstance: any, filter: Function) {
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)
}
}
}
}
function pruneCacheEntry (
cache: VNodeCache,
key: string,
keys: Array,
current?: VNode
) {
const cached = cache[key]
if (cached && (!current || cached.tag !== current.tag)) {
cached.componentInstance.$destroy()
}
cache[key] = null
remove(keys, key)
}
```
在每次渲染即`render`時,首先獲取第一個子元件,之後便是獲取子元件的配置資訊,獲取其資訊,判斷該元件在渲染之前是否符合過濾條件,不需要快取的便直接返回該元件,符合條件的直接將該元件例項從快取中取出,並調整該元件在`keys`陣列中的位置,將其放置於最後,如果快取中沒有該元件,那麼將其加入快取,並且定義了`max`並且快取元件數量如果超出`max`定義的值則將第一個快取的`vnode`移除,之後返回元件並渲染。
```javascript
export default {
render () {
const slot = this.$slots.default
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
if (cache[key]) {
vnode.componentInstance = cache[key].componentInstance
// make current key freshest
remove(keys, key)
keys.push(key)
} else {
cache[key] = vnode
keys.push(key)
// prune oldest entry
if (this.max && keys.length > parseInt(this.max)) {
pruneCacheEntry(cache, keys[0], keys, this._vnode)
}
}
vnode.data.keepAlive = true
}
return vnode || (slot && slot[0])
}
}
```
## 每日一題
```
https://github.com/WindrunnerMax/EveryDay
```
## 參考
```
https://zhuanlan.zhihu.com/p/85120544
https://cn.vuejs.org/v2/api/#keep-alive
https://juejin.im/post/6844904082038063111
https://juejin.im/post/6844903919273918477
https://juejin.im/post/6844904099272458253
https://juejin.im/post/6844904160962281479
https://fullstackbb.com/vue/deep-into-keep-alive-in-vuejs/
https://blog.liuyunzhuge.com/2020/03/20/%E7%90%86%E8%A7%A3vue%E4%B8%ADkeep-alive%E7%BB%84%E4%BB%B6%E6%BA%90%E7%A0%