1.写在前面上一篇介绍了Vue.js通过渲染器实现组件化的能力,介绍了有状态组件和无状态组件的构建与实现,以及异步组件对于框架的意义。本文将主要介绍Vue.js内置的重要组件和模块——KeepAlive组件。2、KeepAlive组件KeepAlive字面意思是保持活动状态,意思是建立持久连接,可以避免组件或连接的频繁创建和销毁。在上面的代码中,Tab组件会根据currentTab变量的值进行频繁的切换,从而导致相应的Tab组件被不断卸载重建。为了避免由此带来的性能开销,可以使用KeepAlive组件使组件保持新鲜。那么KeepAlive组件是如何保持组件存活的呢?实际上,它会将组件缓存起来,避免组件的频繁卸载和重建。实际上是通过一个隐藏的组件缓存容器,在需要的时候将组件放入容器,需要重建的时候取出,让用户感知为“卸载”和“重建”组件.当组件被移入缓存容器并移出时,对应组件激活和去激活的生命周期。3、组件停用和激活那么,如何实现组件缓存管理呢?constKeepAlive={//keepAlive组件的标识符_isKeepAlive:true,setup(props,{slots}){//缓存容器constcache=newMap();常量实例=currentInstance;const{move,createElement}=instance.keepAliveCtx;//隐藏容器conststorageContainer=createElement("div");instance._deActivate=(vnode)=>{move(vnode,storageContainer)};instance._activate=(vnode,container,anchor)=>{move(vnode,container,anchor)};return()=>{letrawNode=slots.default();//非组件虚拟节点无法保持存活if(typeofrawNode.type!=="object"){returnrawNode;}//挂载时先获取缓存的组件vnodeconstcacheVNode=cache.get(rawNode.type);if(cacheVNode){rawVNode.component=cacheVNode.component;rawVNode.keptAlive=true;}else{cache.set(rawVNode.type,rawVNode);}rawVNode.shouldKeepAlive=true;原始VNode.keepAliveInstance=实例;//渲染组件vnodereturnrawVNode}}}上面代码中,KeepAlive组件本身不会渲染额外的内容,渲染函数只返回KeepAlive组件,称为“内部组件”,KeepAlive会添加一个tag属性给“内部组件”的Vnode对象,方便渲染器执行特定逻辑。shouldKeepAlive属性将被添加到“内部组件”的vnode对象中。当渲染器卸载“内部组件”时,可以通过检查“内部组件”属性是否需要KeepAlive获知。keepAliveInstance:内部组件的vnode对象会持有keepAlive组件实例,在unmount函数中通过keepAliveInstance访问_deactivate函数。keepAlive:如果内部组件已经被缓存,则添加keepAlive标签,判断内部组件重新渲染时是否需要重新挂载或激活。functionunmount(vnode){if(vnode.type===Fragment){vnode.children.forEach(comp=>unmount(comp));返回;}elseif(typeofvnode.type==="object"){if(vnode.shouldKeepAlive){vnode.keepAliveInstance._deactivate(vnode);}else{unmount(vnode.component.subTree);}return}constparent=vnode.el.parentVNode;如果(父母){parent.removeChild(vnode.el);}}组件去激活的本质是将组件渲染的内容移到隐藏容器中,激活的本质是将组件要渲染的内容从隐藏容器移回原来的容器。const{move,createElement}=instance.keepAliveCtx;instance._deActivate=(vnode)=>{move(vnode,storageContainer);}instance._activate=(vnode,container,anchor)=>{移动(vnode,container,anchor);}4.include和exclude我们看到上面的代码会缓存组件的所有“内部组件”,但是用户想要自定义缓存规则,只缓存特定的组件。为此,KeepAlive组件需要支持两个属性:include和exclude。include:用于显式配置应该被缓存的组件exclude:用于显式配置不应该被缓存的组件constcache=newMap();constkeepAlive={__isKeepAlive:true,props:{include:RegExp,exclude:RegExp},setup(props,{slots}){//...return()=>{letrawVNode=slots.default();}if(typeofrawVNode.type!=="object"){返回rawVNode;}constname=rawVNode.type.name;if(name&&((props.include&&!props.include.test(name))||(props.exclude&&props.include.test(name)))){//不缓存直接渲染内部组件returnrawVNode}}}}上面代码中为了方便说明问题,设置了正则类型的值。KeepAlive组件挂载时,会根据“内部组件”的名称进行匹配。根据匹配结果判断是否缓存组件。5.缓存管理在上一节中,Map对象用于缓存组件。Map的键值对对应组件的vnode.type属性值和描述组件的vnode对象。因为用来描述组件的vnode对象有一个组件实例的引用,缓存用来描述组件的vnode对象就相当于缓存了组件实例。上面描述的keepAlive组件实现缓存的处理逻辑是:当缓存存在时,继承组件实例,将描述该组件的vnode对象标记为keepAlive,渲染器不会重新创建新的组件实例。当缓存不存在时,设置缓存。但是,如果缓存不存在时,总会设置一个新的缓存,这样会导致缓存不断增加,占用大量内存。为此,我们需要设置一个内存阈值。当缓存数量超过指定阈值时,需要对缓存进行剪枝。在Vue.js中,使用了“最新访问”策略。“最新访问”策略本质上是将当前访问或渲染的组件设置为最新渲染的组件,该组件在剪枝过程中始终是安全的,即不会被剪枝。缓存实例需要满足固定格式:const_cache=newMap();常量缓存:KeepAliveCache={get(key){_cache.get(key);},set(key,value){_cache.set(key,value);},删除(键){_cache.delete(键);},forEach(fn){_cache.forEach(fn);}}6.写在最后,本文简单介绍一下Vue.js中KeepAlive组件的设计,根据实现原理,实现组件的缓存,避免组件实例的不断销毁和重构。当KeepAlive组件被卸载时,渲染器并没有真正卸载它,而是将组件移动到另一个隐藏的容器中,让组件保持当前状态。挂载KeepAlive组件后,渲染器将其从隐藏容器移动到原始容器。此外,我们讨论了包含和排除KeepAlive组件的自定义缓存,以及缓存管理。