14.1 KeepAlive组件的实现原理
KeepAlive的本质是缓存管理,再加上特殊的挂载/卸载逻辑。KeepAlive组件的实现需要渲染器层面的支持。在keepalive组件卸载时我们不能正的将他卸载,
const cache = new Map();//存储【key,value】 key为插槽组件的vnode.type 而value为插槽组件的vnode
const KeepAlive = {
__isKeepAlive: true, // KeepAlive组件独有的属性,用作标识
props: {
include: RegExp, // KeepAlive的两个属性
exclude: RegExp
},
setup(props, { slots }) {
const instance = currentInstance // 渲染的时候当前的渲染组件的实例,就是KeepAlive本身的实例
const { move, createElement } = instance.keepAliveCtx // keepAlive特有的,move是将移动到一个容器中,
const storageContainer = createElement('div') // 创建隐藏容器
instance._deActivate = (vnode) => { // 隐藏时候的函数
move(vnode, storageContainer) //移动到隐藏容器
}
instance._activate = (vnode, container, anchor) => {
move(vnode, container, anchor) // 移除来
}
return () => {
let rawVNode = slots.default() //keepalive的默认插槽
if (typeof rawVNode.type !== 'object') { // 默认插槽的内容只能是一个组件,并且不能是函数式组件
return rawVNode //如果不是直接渲染 没有keepAlive的效果
}
const name = rawVNode.type.name // 插槽组件的名字
if (
name &&
(
(props.include && !props.include.test(name)) ||
(props.exclude && props.exclude.test(name))
) //没有名字或者在排查缓存中的组件,也是没有KeepAlive
) {
return rawVNode
}
const cachedVNode = cache.get(rawVNode.type);//获取桶中有没有已经缓存了,插槽中的组件
if (cachedVNode) { //如果已经缓存了
rawVNode.component = cachedVNode.component //继承旧的组件实例
rawVNode.keptAlive = true // 已经被缓存标志
} else {
cache.set(rawVNode.type, rawVNode) //没有缓存就缓存他
}
rawVNode.shouldKeepAlive = true //插槽中的组件中,避免渲染器将组件卸载,调用KeepAlive上的_deActivate搬运部
rawVNode.keepAliveInstance = instance //插槽中的组件添加KeepAlive的组件实例
return rawVNode //返回职这个被修改后的插槽Vnode
}
}
}
KeepAlive的instance上有两个函数_deActivate
和_activate
,
KeepAlive的插槽中的组件的Vnode被KeepAlive插入shouldKeepAlive
和keepAliveInstance
当组件的卸载的时候:
function unmount(vnode) {
if (vnode.type === Fragment) {
vnode.children.forEach(c => unmount(c))
return
} else if (typeof vnode.type === 'object') {
if (vnode.shouldKeepAlive) { //如果这个组件是被KeepAlive的组件
vnode.keepAliveInstance._deActivate(vnode) // 调用隐藏组件的方法
} else {
unmount(vnode.component.subTree)
}
return
}
const parent = vnode.el.parentNode
if (parent) {
parent.removeChild(vnode.el)
}
}
当要重新挂载的时候:
else if (typeof type === 'object' || typeof type === 'function') {
// component
if (!n1) {
if (n2.keptAlive) { //如果新组件已经缓存了
n2.keepAliveInstance._activate(n2, container, anchor) ;//调用_activate重新挂载
} else {
mountComponent(n2, container, anchor)
}
} else {
patchComponent(n1, n2, anchor)
}
}