vue的keepalive组件是怎么缓存组件的?

69 阅读3分钟

首先,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]

以上就是个人一点源码理解,如有疑问或不对的地方,欢迎交流