首先,KeepAlive 组件的实现需要渲染器层面的支持。这是因为被KeepAlive 的组件在卸载时,我们不能真的将其卸载,否则就无法维持组件的当前状态了。正确的做法是,将被 KeepAlive 的组件从原容器搬 运到另外一个隐藏的容器中,实现“假卸载”。当被搬运到隐藏容器中的组件需要再次被“挂载”时,我们也不能执行真正的挂载逻辑,而应该把该组件从隐藏容器中再搬运到原容器。这个过程对应到组件的生命周期,其实就是 activated 和 deactivated
通过什么缓存dom
下面是 keepalive setup源码的部分代码,做了简化
// 获得keepalive实例
const instance = getCurrentInstance()!
// 实例上下文
const sharedContext = instance.ctx as KeepAliveContext
const cache: Cache = new Map() // key为组件或vnode.key或vnode.type 值为缓存keepalive子组件虚拟dom(给就是keepalive中的内容)
const keys: Keys = new Set()
// 缓存dom
const storageContainer = createElement('div')
let pendingCacheKey
// 挂载时保存
onMounted(() => {
cache.set(pendingCacheKey, getInnerChild(instance.subTree))
} )
// 为上下文添加两个方法
// 组件挂载时调用将组件添加到容器中,
// 将内容添加到渲染出来
//if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
// ;(parentComponent!.ctx as KeepAliveContext).activate(
// n2,
// container,
// anchor,
// isSVG,
// optimized
// )
// return
// }
sharedContext.activate = (vnode, container, anchor, isSVG, optimized) => {
const instance = vnode.component!
move(vnode, container, anchor, MoveType.ENTER, parentSuspense)
// ...
}
// 组件要销毁时,调用这个,但不是真的销毁,只是将内容中的组件添加到了storageContainer这个div容器中
// vnode卸载时unmount中有下面这段代码
// if (shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE) {
// ;(parentComponent!.ctx as KeepAliveContext).deactivate(vnode)
// return
// }
sharedContext.deactivate = (vnode: VNode) => {
const instance = vnode.component
move(vnode, storageContainer, null, MoveType.LEAVE, parentSuspense)
// ...
}
//
return () => {
const children = slots.default()
const rawVNode = children[0]
let vnode = getInnerChild(rawVNode) // 获得keepalive的vnode
// 表明keepalive只能用于组件
const comp = vnode.type as ConcreteComponent
const key = vnode.key == null ? comp : vnode.key
if (children.length > 1) {
// 表明keepalive只能有一个子组件
if (__DEV__) {
warn(`KeepAlive should contain exactly one component child.`)
}
current = null
return children
}
// 通过key获得缓存vnode
pendingCacheKey = key
const cachedVNode = cache.get(key)
if (cachedVNode) {
存在直接用缓存里的vnode替换
vnode.el = cachedVNode.el
vnode.component = cachedVNode.component
// 表明已渲染过的组件,再挂载时用activate
vnode.shapeFlag |= ShapeFlags.COMPONENT_KEPT_ALIVE
} else {
// 添加key
keys.add(key)
}
// 用于识别为一个keepalive组件,卸载时调用deactivate
vnode.shapeFlag |= ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
return vnode
}
上面代码做了很多简化,主要是说清楚keepalive组件的整个核心逻辑
首先拿到其实例,得到其上下文sharedContext,定义缓存slots的容器cache,定义隐藏的dom的容器storageContainer(创建div),然后在sharedContext定义两个方法activate和deactivate,这个两个方法会在渲染器中调用,因为keepalive中的组件不是真的被卸载,所以会在挂载和卸载时,调用这两个方法,将真实dom保存和移出storageContainer中
1、缓存的dom放到哪里去了 答:内部创建了一个div元素,但没有挂载到html中,keepalive将卸载的组件全添加到这个div中,并内部也缓存了组件vnode,vnode中有元素dom引用vnode.el
2、keepalive独有有生命周期activated和deactivated怎么实现的 答:创建keepalive实例的上下文件中添加两个方法activate和deactivate,这个在组件挂载和卸载时分别调用,这个也会在其中调用这个两个生命周期
3、keepalive中是怎么获取slot组件的
const children = slots.default()
const rawVNode = children[0]
以上就是个人一点源码理解,如有疑问或不对的地方,欢迎交流