time: 2021-05-12 10:15:48
author: heyunjiang
缓存
不活动的动态组件实例,而不是销毁他们,在下次激活的时候,用户会看到之前操作的状态被保留activated
和 deactivated
2个生命周期钩子只允许有一个
,超出则组件不会被缓存// 编译前
<keep-alive>
<component :is="currentComponents" :hello="1" />
</keep-alive>
// 编译后
_c('keep-alive',[_c(_vm.currentComponents,{tag:"component",attrs:{"hello":1}})
还是在 createElement 中来看,在查找实例 vm.$options.components
时,能找到 keep-alive
组件,我们来看看它是如何控制的
在组件通过 createElement、createComponents 生成 vnode 之后,通过 patch.createComponent 渲染中,会调用 componentVnodeHooks.init 来初始化组件,keep-alive 组件也一样
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 */)
}
if (isDef(vnode.componentInstance)) {
initComponent(vnode, insertedVnodeQueue)
insert(parentElm, vnode.elm, refElm)
if (isTrue(isReactivated)) {
reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
}
return true
}
}
}
componentVNodeHooks 组件 vnode 初始化、销毁、更新相关入口
const componentVNodeHooks = {
// 初始化组件入口
init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
if (
vnode.componentInstance &&
!vnode.componentInstance._isDestroyed &&
vnode.data.keepAlive
) {
// kept-alive 组件更新
const mountedNode: any = vnode
componentVNodeHooks.prepatch(mountedNode, mountedNode)
} else {
// 所有组件初次渲染,实例化组件
const child = vnode.componentInstance = createComponentInstanceForVnode(
vnode,
activeInstance // 表示当前渲染组件的环境
)
child.$mount(hydrating ? vnode.elm : undefined, hydrating)
}
},
prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
const options = vnode.componentOptions
const child = vnode.componentInstance = oldVnode.componentInstance
updateChildComponent(
child,
options.propsData, // updated props
options.listeners, // updated listeners
vnode, // new parent vnode
options.children // new children
)
},
// 销毁组件入口
destroy (vnode: MountedComponentVNode) {
const { componentInstance } = vnode
if (!componentInstance._isDestroyed) {
if (!vnode.data.keepAlive) {
componentInstance.$destroy()
} else {
deactivateChildComponent(componentInstance, true /* direct */)
}
}
}
}
调用 init 方法后,keep-alive 组件生成了自己的 vm 实例,调用自身 vm.$mount 开始渲染组件。
走到 vm.$mount,会按正常组件渲染
vnode.data.keepAlive = true
, 缓存在 keep-alive vm.cache
中来看看 keep-alive 组件部分源码
export default {
name: 'keep-alive',
abstract: true,
props: {
include: patternTypes,
exclude: patternTypes,
max: [String, Number]
},
created () {
this.cache = Object.create(null)
this.keys = []
},
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])
}
}
keepalive 壳子主要功能
lifecycle.js deactivateChildComponent 组件流程
export function deactivateChildComponent (vm: Component, direct?: boolean) {
if (!vm._inactive) {
vm._inactive = true
for (let i = 0; i < vm.$children.length; i++) {
deactivateChildComponent(vm.$children[i])
}
callHook(vm, 'deactivated')
}
}
vnode.componentInstance = oldVnode.componentInstance
componentVNodeHooks.prepatch
方法,执行 updateChildComponent, updateChildComponent 作为 keep-alive 子组件的独有渲染方法vnode.elm = vnode.componentInstance.$el
生成 vnode.elm,然后 insert 插入 parentNode 节点,完成渲染lifecycle.js updateChildComponent 源码
export function updateChildComponent (
vm: Component,
propsData: ?Object,
listeners: ?Object,
parentVnode: MountedComponentVNode,
renderChildren: ?Array<VNode>
) {
vm.$options._parentVnode = parentVnode
vm.$vnode = parentVnode // update vm's placeholder node without re-render
if (vm._vnode) { // update child tree's parent
vm._vnode.parent = parentVnode
}
vm.$options._renderChildren = renderChildren
vm.$attrs = parentVnode.data.attrs || emptyObject
vm.$listeners = listeners || emptyObject
// update props
if (propsData && vm.$options.props) {
toggleObserving(false)
const props = vm._props
const propKeys = vm.$options._propKeys || []
for (let i = 0; i < propKeys.length; i++) {
const key = propKeys[i]
const propOptions: any = vm.$options.props // wtf flow?
props[key] = validateProp(key, propOptions, propsData, vm)
}
toggleObserving(true)
// keep a copy of raw propsData
vm.$options.propsData = propsData
}
// update listeners
listeners = listeners || emptyObject
const oldListeners = vm.$options._parentListeners
vm.$options._parentListeners = listeners
updateComponentListeners(vm, listeners, oldListeners)
}