結合例子有如下狀況html
<keep-alive>
<coma v-if="visible"></coma>
<comb v-else></comb>
</keep-alive>
<button @click="visible = !visible">更改</button>
複製代碼
例如在coma
和comb
都有一個input
都有對應的value
,若是咱們不用keep-alive
,當更改visible
的時候,這兩個組件都會從新渲染,先前輸入的內容就會丟失,會執行一遍完整的生命週期流程:beforeCreate
=> created
...。
可是若是咱們用了keep-alive
,那麼在次切換visible
的時候,input
對應的value
爲上次更改時候的值。 因此keep-alive
主要是用於保持組件的狀態,避免組件反覆建立。vue
keep-alive
的使用方法定在core/components/keep-alive
中node
export default {
abstract: true,
props: {
include: patternTypes, // 緩存白名單
exclude: patternTypes, // 緩存黑名單
max: [String, Number] // 緩存的實例上限
},
created() {
// 用於緩存虛擬DOM
this.cache = Object.create(null);
this.keys = [];
},
mounted() {
// 用於監聽i黑白名單,若是發生調用pruneCache
// pruneCache更新vue的cache緩存
this.$watch('include', val => {
pruneCache(this, name => matches(val, name))
})
this.$watch('exclude', val => {
pruneCache(this, name => !matches(val, name))
})
}
render() {
//...
}
}
複製代碼
上面代碼中定義了多個聲明週期的操做,最重要的render
函數,下面來看看是如何實現的git
render
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 // 定義組件的緩存key
// 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) // 調整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])
}
複製代碼
進行分步驟進行分析github
keep-alive
對象包括的第一個子組件對象vnode
vnode
的cid
和tag
生成的key
,在緩存對象中是否有當前緩存,若是有則返回,並更新key
在keys
中的位置cache
添加這個的內容,而且根據LRU
算法刪除最近沒有使用的實例keep-alive
爲true
結合文章開頭的文章進行分析當前例子,當頁面首次渲染的時候,由於組件的渲染過程是先子組件後父組件的,因此這裏就能拿到子組件的數據,而後把子組件的vnode
信息存儲到cache
中,而且把coma
組件的keepAlive
的置爲true
。 這個有個疑問,爲何能拿到子組件的componentOptions
,藉助上面個例子,咱們知道生成vnode
是經過render
函數,render
函數是經過在platforms/web/entry-runtime-with-compiler
中定義,經過compileToFunctions
將template
編譯爲render
函數,看一下生成的對應render
函數web
<template>
<div class="parent">
<keep-alive>
<coma v-if="visible"></coma>
<comb v-else></comb>
</keep-alive>
</div>
</template>
<script> (function anonymous() { with(this) { return _c('div', { staticClass: "parent" }, [ _c('keep-alive', [(visibility) ? _c('coma') : _c('comb')], 1), _c('button', { on: { "click": change } }, [_v("change")])], 1) } }) </script>
複製代碼
能夠看到生成的render
函數中有關keep-alive
的生成過程算法
_c('keep-alive', [(visibility) ? _c('coma') : _c('comb')], 1),
複製代碼
在keep-alive
中先調用了_c('coma')
,因此才能訪問到到子組件的componentOptions
,具體的_c
是在vdom/create-element.js
中定義,他判斷是生成組件vnode
仍是其餘的。數組
data
,觸發patch
在首次渲染的時候,咱們更改coma
中的input
的值,看當visible
再次更改成true
的時候,input
是否會記住先前的值。由於更改了visible
的值後,會從新執行這段代碼緩存
updateComponent = () => {
vm._update(vm._render())
}
複製代碼
因此就會從新執行keep-alive
的render
函數,由於在首次渲染的時候已經把數據存入到cache
中,因此此次數據直接從cache
中獲取執行。bash
vnode.componentInstance = cache[key].componentInstance
複製代碼
在首次渲染的時候提到當key
值不存在的時候會先將子組件的vnode
緩存起來,若是經過打斷點的方式能夠看到首次渲染的時候componentInstance
爲undefined
,componentInstance
實際是在patch
過程當中調用組件的init
鉤子才生成的,那麼爲何這個時候能拿到呢,這裏經過一個例子來進行講解例若有下面例子
a = {
b: 1
}
c = a;
a.b = 5;
console.log(c.b) // 5
複製代碼
object
是引用類型,因此原對象發生更改的時候引用的地方也會發生改變
那麼就把先前的狀態信息從新賦值給了coma
,而後爲何賦值給了coma
,coma
的就不會執行組件的建立過程呢,看patch
的代碼,當執行到createComponent
的時候,由於coma
爲組件,就會執行組件相關的邏輯
// core/vdom/patch.js
function createComponent(vnode, insertedVnodeQueue, parentElm, refELm) {
let i = vnode.data;
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false);
}
}
}
// core/vdom/create-component
init(vnode) {
if (vnode.componentInstance &&
!vnode.componentInstance._isDetroyed &&
vnode.data.keepAlive) {
const mountedNode: any = node;
componentVnodeHooks.prepatch(mountedNode, mountedNode)
} else {
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
)
child.$mount(vnode.elm)
}
}
複製代碼
由於vnode.componentInstance
在keep-alive
已經進行了從新賦值,因此而且keepAlive
爲true
,因此只會執行prepatch
,因此created
、mounted
鉤子都不會執行。
keep-alive
自己建立和patch
過程在core/instance/render
中,能夠看到updateComponent
的定義
updateComponent = () => {
vm._update(vm._render())
}
複製代碼
因此首先調用keep-alive
的render
函數生成vnode
,而後調用vm._update
執行patch
操做,那麼keep-alive
和普通組件在首次建立的時候和patch
過程當中有什麼差別呢?
無論keep-alive
是否是抽象組件,他終究是個也是個組件,因此也會執行組件相應的邏輯,在首次渲染的時候執行patch
操做,執行到core/vdom/patch
中
function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
let i = vnode.data
if (isDef(i)) {
const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
if (isDef(i = i.hook) && isDef(i = i.init)) {
i(vnode, false /* hydrating */)
}
}
}
複製代碼
由於是首次渲染因此componentInstance
並不存在,因此只執行了init
鉤子,init
的具體做用就是建立子組件實例。
但keep-alive
畢竟是抽象組件,那抽象組件和正常組件區別體如今哪兒呢? 在core/instance/lifecycle
中能夠看到,不是抽象組件的時候纔會往父組件中加入自己,,而且子組件也不會往抽象組件$children
中加入本身。這個函數又是在vm._init
中進行調用的
let parent = options.parent
if (parent && !options.abstract) {
while (parent.$options.abstract && parent.$parent) {
parent = parent.$parent
}
parent.$children.push(vm)
}
複製代碼
patch
過程結合上面的例子,當visible
發生更改的時候,會影響到keep-alive
組件嗎,在patch
的那片文章提到過當data
中的值發生改變的時候,會觸發updateComponent
updateComponent = () => {
vm._update(vm._render())
}
複製代碼
就會從新執行keep-alive
的render
函數,從新執行根組件的patch
過程,具體的原理課參照Vue 源碼patch過程詳解,這裏就直接執行了keep-alive
組件的prepatch
鉤子
這裏有個問題須要解決一下,每次到達下一個tick
的時候都須要進行從新生成vnode
,這裏有什麼辦法優化嗎,能不能用其餘方式來替換,仍是說必須這麼作?小夥伴能想到什麼好的辦法嗎?
keep-alive
是不是必須的能夠看到keep-alive
對於緩存數據是有巨大幫助的,而且能夠防止組件反覆建立。那麼就有問題了,是否絕大多數組件均可以使用keep-alive
用於提升性能。
keep-alive
keep-alive
是否有必要,若是兩個組件切換是不須要保存狀態的,那還須要嗎。你可能說用keep-alive
能節省性能,那咱們在須要在activated
重置這些屬性。這樣作有幾點風險
cache
中,當組件過多的時候內容過多,就致使這個對象巨大,還能起到提升性能的需求嗎,這個表示懷疑態度Vue
的源碼分析的文章會一直更新,麻煩關注一下個人github