原理:
将被KeepAlive的组件从原容器搬运到另一个隐藏的容器中,实现“假卸载“,当被搬运到隐藏容器中的组件需要再次被挂载时,我们也不能执行真正的挂载逻辑,而是把组件从隐藏容器中再搬运到原容器,这个过程对应到组件的生命周期,就是activated和deactivated
一个简单的KeepAlive组件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
| const KeepAlive = { __isKeepAlive: true, setup(props, { slots }) { const cache = new Map(); const instance = currentInstance; const { move, createElement } = instance.KeepAliveCtx;
const storageContainer = createElement("div");
instance._deActivate = (vnode) => { move(vnode, storageContainer); }; instance._activate = (vnode, container, anchor) => { move(vnode, container, anchor); }; return () => { let rawVNode = slots.default(); if (typeof rawVNode.type !== "object") { 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; rawVNode.KeepAliveInstance = instance; return rawVNode; }; }, };
|
KeepAlive组件会对内部组件进行操作,主要是在内部组件的vnode对象上添加一些标记属性,以便渲染器能够执行特定的逻辑,这些标记属性包括:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| function unmount(vnode) { if(vnode.type === Fragment) { vnode.children.forEach(c=>unmount(c)) return }else if(typeof vnode.type === 'object') { if(vnode.shouldKeepAlive) { vnode.KeepAliveInstance._deActivate(vnode) }else { unmount(vnode.component.subTree) } return } const parent = vnode.el.parentNode if(parent) { parent.removeChild(vnode.el) } }
|
shouldKeepAlive:该属性会被添加到内部组件的vnode对象,这样当渲染器卸载内部组件时,可以通过检查该属性得知,内部组件需要被KeepAlive,于是渲染器不会真的卸载内部组件,而是会调用_deActivate函数完成搬运工作
keptAlive:内部组件如果已经被缓存,则还会为其添加一个keptAlive标记,这里当内部之间需要重新渲染时,渲染器并不会重新挂载它,而会将其激活
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| function patch(n1,n2,container,anchor) { if(n1 && n1.type !== n2.type) { unmount(n1) n1=null } const {type} = n2 if(typeof type === 'string'){ }else if(type === Text){
}else if(type === Fragment){ }else if(typeof type === 'object' || typeof type === 'function') { if(!n1) { if(n2.keptAlive){ n2.keepAliveInstance._activate(n2,container,anchor) }else { mountComponent(n2,container,anchor) } }else { patchComponent(n1,n2,anchor) } }
}
|
失活的本质就是将组件所渲染的内容移动到隐藏容器中,激活的本质是将组件所渲染的内容搬运到原来的容器
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| function mountComponent(vnode,container,anchor) { const instance = { state, props: shallowReactive(props), isMounted: false, subTree: null, slots, mounted: [], keepAliveCtx: null } const isKeepAlive = vnode.type.__isKeepAlive if(isKeepAlive) { instance.keepAliveCtx = { move(vnode,container,anchor) { insert(vnode.component.subTree.el,container,anchor) }, createElement } } }
|
用LRU算法实现缓存管理,可以为缓存设置最大容量
1 2 3
| <KeepAlive :max="2"> <component :is="dynamicComp"></component> </KeepAlive>
|
LRU算法的思想就是会把当前访问(或渲染)的组件作为最新一次渲染的组件,移动到队头,而如果队列容量不够,会把最久未被使用的组件即队尾组件移出队列,也可以自定义缓存实例:
1 2 3
| <KeepAlive :cache="cache"> <component :is="dynamicComp"></component> </KeepAlive>
|