Appearance
原理
根据组件的tag和keys来生成具有LRU的缓存的map,通过添加abstract属性,避免出现在$parent链中,每次渲染时总是检查缓存是否存在,如果存在render函数直接返回之前的vnode并添加缓存。返回新的vnode后在keep-alive的patch阶段又会重新走createComponent子组件的操作,就会进入之前的缓存逻辑
流程
在当前实例中vnode如果是是组件(keep-alive),createComponentInstanceForVnode(),示例化keep-alive。keep-alive在初始化中总是绘制触发render-watcher(this.value = this.lazy ? undefined : this.get())。触发了updateComponent(vm._render()), 也就是renderkeep-alive的render函数()
keep-alive render中,总是检查this.$slots.default(总是一个vnode)的name是否在keep-alive库中,如果在库中检查是否缓存了vnode,没有则缓存,如果有则将componentInstance赋值到组件上,添加vnode.data.keep-alive = true
在keep-alive 的 createComponent 阶段,由于data.keepAlive 存在并且组件实例存在,直接进入prepatch 阶段,不需要走后面的子组件挂载等操作, 否则按照正常的组件流程走创建子组件挂载等。
源码
组件的源码比较简单,有两个需要主要的点吗,设置abstract:true 还是能通过parent属性获取到keep-alive组件示例,对于手动的修改缓存队列或者在自定义缓存时有用。keep-alive的render实际是返回一个vnode,如果能够根据key查到缓存则返回之前的vnode。
js
// keep-alive.vue
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])
}核心在下面的createComponent中,针对keepAlive包裹的组件,在重新渲染时vnode因为是之前的vnode,会触发init额外的流程。进而直接到prepatch阶段
js
// vnode指的是根据keep-alive render函数得到的包裹组件的vnode,拿到vnode。
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
var i = vnode.data;
if (isDef(i)) {
// isReactivated用来判断组件是否缓存。
var isReactivated = isDef(vnode.componentInstance) && i.keepAlive;
if (isDef(i = i.hook) && isDef(i = i.init)) {
// 执行组件初始化的内部钩子 init,下面的componentVNodeHooks.init
i(vnode, false /* hydrating */);
}
if (isDef(vnode.componentInstance)) {
// 其中一个作用是保留真实dom到vnode中
initComponent(vnode, insertedVnodeQueue);
insert(parentElm, vnode.elm, refElm);
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm);
}
return true
}
}
}
var componentVNodeHooks = {
init: function init (vnode, hydrating) {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive components, treat as a patch
var mountedNode = vnode; // work around flow
componentVNodeHooks.prepatch(mountedNode, mountedNode);
} else {
// 将组件实例赋值给vnode的componentInstance属性
var child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance
);
child.$mount(hydrating ? vnode.elm : undefined, hydrating);
}
},
// 后面分析
prepatch: function prepatch (oldVnode, vnode) {
// 新组件实例
var options = vnode.componentOptions;
// 旧组件实例
var child = vnode.componentInstance = oldVnode.componentInstance;
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
);
}
}