这篇文章主要介绍“Vue中的KeepAlive组件怎么使用”,在日常操作中,相信很多人在Vue中的KeepAlive组件怎么使用问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”Vue中的KeepAlive组件怎么使用”的疑惑有所帮助!接下来,请跟着小编一起来学习吧!
是一个内置组件,它的功能是在多个组件间动态切换时缓存被移除的组件实例。KeepAlive 一词借鉴于 HTTP 协议,在 HTTP 协议里面 KeepAlive 又称持久连接,作用是允许多个请求/响应共用同一个 HTTP 连接,解决了频繁的销毁和创建 HTTP 连接带来的额外性能开销。而同理 Vue 里的 KeepAlive 组件也是为了避免一个组件被频繁的销毁/重建,避免了性能上的开销。
//App.vuetoggle
上述代码可以看到,如果我们频繁点击 toggle 时会频繁的渲染 Test/HelloWorld 组件,当用户频繁的点击时 Test 组件需要频繁的销毁/渲染,这就造成很大的渲染性能损失。所以为了解决这种性能开销,你需要知道是时候使用 KeepAlive 组件。
toggle
可以看这个录屏,在首次加载后再次频繁的切换并没有重新销毁与挂载,而仅仅是将组件进行了失活(而不是销毁),渲染时只需要重新激活就可以,而不需重新挂载,如果要渲染的组件很大,那就能有不错的性能优化。想要体验的话可以去看看这个例子?官方demo,其中数据会被缓存这个也需要在开发使用中去注意到的实现原理其实很简单,其实就是缓存管理和特定的销毁和渲染逻辑,使得它不同于其他组件。KeepAlive 组件在卸载组件时并不能真的将其卸载,而是将其放到一个隐藏的容器里面,当被激活时再从隐藏的容器中拿出来挂载到真正的 dom 上就行,这也就对应了 KeepAlive 的两个独特的生命周期activated
和deactivated
。所以在 KeepAlive 内的子组件在 mount 和 unmount 的时候会执行特定的渲染逻辑,从而不会去走挂载和销毁逻辑
constKeepAliveImpl:ComponentOptions={ name:"KeepAlive", //标识这是一个KeepAlive组件 __isKeepAlive:true, //props props:{ exclude:[String,Array,RegExp], include:[String,Array,RegExp], max:[String,Number] } } //isKeepAlive exportconstisKeepAlive=(vnode:VNode):boolean=> (vnode.typeasany).__isKeepAlive
//setup接着上面的代码 //获取到当前KeepAlive组件实例 co免费云主机域名nstinstance=getCurrentInstance()!asany; //拿到ctx constsharedContext=instance.ctxasKeepAliveContext; //cache缓存 //key:vnode.key|vnode.typevalue:vnode constcache:Cache=newMap() //需要拿到某些的renderer操作函数,需要自己特定执行渲染和卸载逻辑 const{renderer:{p:patch,m:move,um:_unmount,o:{createElement}}}=sharedContext //隐藏的容器,用来存储需要隐藏的dom conststoreageContainer=createElement('div') //存储当前的子组件的缓存key letpendingKey:CacheKey|null=null sharedContext.activate=(vnode,container,anchor)=>{ //KeepAlive下组件激活时执行的move逻辑 move(vnode,container,anchor,0/*ENTER*/) } sharedContext.deactivate=(vnode)=>{ //KeepAlive下组件失活时执行的move逻辑 move(vnode,storeageContainer,null,1/*LEAVE*/) } return()=>{ //没有子组件 if(!slots.default){ returnnull; } constchildren=slots.default()asVNode[]; constrawNode=children[0]; letvnode=rawNode; constcomp=vnode.typeasConcreteComponent; constname=comp.displayName||comp.name const{include,exclude}=props; //没有命中的情况 if( (include&&(!name||!matches(include,name)))|| (exclude&&name&&matches(exclude,name)) ){ //直接渲染子组件 returnrawNode; } //获取子组件的vnodekey constkey=vnode.key==null?comp:vnode.key; //获取子组件缓存的vnode constcachedVNode=cache.get(key); pendingKey=key; //命中缓存 if(cachedVNode){ vnode.el=cachedVNode.el; //继承组件实例 vnode.component=cachedVNode.component; //在vnode上更新shapeFlag,标记为COMPONENT_KEPT_ALIVE属性,防止渲染器重新挂载 vnode.shapeFlag|=ShapeFlags.COMPONENT_KEPT_ALIVE }else{ //没命中将其缓存 cache.set(pendingKey,vnode) } //在vnode上更新shapeFlag,标记为COMPONENT_SHOULD_KEEP_ALIVE属性,防止渲染器将组件卸载了 vnode.shapeFlag|=ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE //渲染组件vnode returnvnode; }
在 KeepAlive 组件内会从 sharedContext 上的 renderer 上拿到一些方法比如 move、createElement 等
functionmountComponent(){ //... if(isKeepAlive(initialVNode)){ ;(instance.ctxasKeepAliveContext).renderer=internals } }
首先从上面可以看到,在渲染 KeepAlive 组件时会对其子组件的 vnode 上增加对应的 shapeFlag 标志比如COMPONENT_KEPT_ALIVE
标志,组件挂载的时候告诉渲染器这个不需要 mount 而需要特殊处理
constprocessComponent=( n1:VNode|null, n2:VNode, container:RendererElement, anchor:RendererNode|null, )=>{ if(n1==null){ //在KeepAlive组件渲染时会对子组件增加COMPONENT_KEPT_ALIVE标志 //挂载子组件时会判断是否COMPONENT_KEPT_ALIVE,如果是不会调用mountComponent而是直接执行activate方法 if(n2.shapeFlag&ShapeFlags.COMPONENT_KEPT_ALIVE){ ;(parentComponent!.ctxasKeepAliveContext).activate( n2, container, anchor ) } //... } }
同理COMPONENT_SHOULD_KEEP_ALIVE
标志也是用来在组件卸载的时候告诉渲染器这个不需要 unmount 而需要特殊处理。
constunmount:UnmountFn=(vnode)=>{ //... //在KeepAlive组件渲染时会对子组件增加COMPONENT_SHOULD_KEEP_ALIVE标志 //然后在子组件卸载时并不会真实的卸载而是调用KeepAlive的deactivate方法 if(shapeFlag&ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE){ ;(parentComponent!.ctxasKeepAliveContext).deactivate(vnode) return } }
首先这两个生命周期是在 KeepAlive 组件内独特声明的,是直接导出使用的。
exportfunctiononActivated( hook:Function, target?:ComponentInternalInstance|null ){ //注册activated的回调函数到当前的instance的钩子函数上 registerKeepAliveHook(hook,LifecycleHooks.ACTIVATED,target) } exportfunctiononDeactivated( hook:Function, target?:ComponentInternalInstance|null ){ //注册deactivated的回调函数到当前的instance的钩子函数上 registerKeepAliveHook(hook,LifecycleHooks.DEACTIVATED,target) }
然后因为这两个生命周期会注册在 setup 里面,所以只要执行 setup 就会将两个生命周期的回调函数注册到当前的 instance 实例上
//renderer.ts //mount函数逻辑 constmountComponent=(initialVNode, container, anchor, parentComponent, parentSuspense, isSVG, optimized )=>{ //... constinstance:ComponentInternalInstance= compatMountInstance|| (initialVNode.component=createComponentInstance( initialVNode, parentComponent, parentSuspense )) //执行setup setupComponent(instance) } //setupcomponent处理setup函数值 exportfunctionsetupComponent( instance:ComponentInternalInstance, isSSR=false ){ //... constisStateful=isStatefulComponent(instance) //... constsetupResult=isStateful //setupStatefulComponent函数主要功能是设置当前的instance ?setupStatefulComponent(instance,isSSR) :undefined //... } functionsetupStatefulComponent( instance:ComponentInternalInstance ){ if(setup){ //设置当前实例 setCurrentInstance(instance) //执行组件内setup函数,执行onActivated钩子函数进行回调函数收集 constsetupResult=callWithErrorHandling( setup, instance, ErrorCodes.SETUP_FUNCTION, [__DEV__?shallowReadonly(instance.props):instance.props,setupContext] ) //currentInstance=null; unsetCurrentInstance() } }
最后在执行sharedContext.activate
和sharedContext.deactivate
的时候将注册在实例上的回调函数取出来直接执行就OK了,执行时机在 postRender 之后
sharedContext.activate=(vnode,container,anchor)=>{ //KeepAlive下组件激活时执行的move逻辑 move(vnode,container,anchor,0/*ENTER*/) //把回调推入到postFlush的异步任务队列中去执行 queuePostRenderEffect(()=>{ if(instance.a){ //a是activated钩子的简称 invokeArrayFns(instance.a) } }) } sharedContext.activate=(vnode,container,anchor)=>{ //KeepAlive下组件失活时执行的move逻辑 move(vnode,container,anchor,0/*ENTER*/) queuePostRenderEffect(()=>{ if(instance.da){ //da是deactivated钩子的简称 invokeArrayFns(instance.da) } }) } exportconstenumLifecycleHooks{ //...其他生命周期声明 DEACTIVATED='da', ACTIVATED='a', } exportinterfaceComponentInternalInstance{ //...其他生命周期 [LifecycleHooks.ACTIVATED]:Function[] [LifecycleHooks.DEACTIVATED]:Function[] }
以下是关于上述demo如何实现的简化流程图KeepAlive 组件的onMounted
和onUpdated
生命周期时进行缓存缓存数量超过设置的 max 时监听 include 和 exclude 修改的时候,会读取缓存中的知进行判断是否需要清除缓存修剪缓存的时候也要 unmount(如果该缓存不是当前组件)或者 resetShapeFlag 将标志为从 KeepAlive 相关 shapeFlag 状态重置为 STATEFUL_COMPONENT 状态(如果该缓存是当前组件,但是被exclude了),当然 unmount 函数内包含 resetShapeFlag 操作KeepAlive 组件的缓存策略是 LRU(last recently used)缓存策略核心思想在于需要把当前访问或渲染的组件作为最新一次渲染的组件,并且该组件在缓存修剪过程中始终是安全的,即不会被修剪。
sharedContext.activate=(vnode,container,anchor)=>{ //instance是子组件实例 constinstance=vnode.component! //... //dev环境下设置,自己模拟写的 devtools.emit('component:added',instance.appContext.app,instance.uid,instance.parent?instance.parent.uid:undefined,instance) //官方添加 if(__DEV__||__FEATURE_PROD_DEVTOOLS__){ //Updatecomponentstree devtoolsComponentAdded(instance) } } //同理sharedContext.deactivates上也要添加,不然不会显示在组件树上
当子组件有 prop 更新时是需要重新去 patch 的,所以在 activate 的时候需要重新执行 patch 进行子组件更新
sharedContext.activate=(vnode,container,anchor)=>{ //... //props改变需要重新patch(update) patch( instance.vnode, vnode, container, anchor, instance, parentSuspense, isSVG, vnode.slotScopeIds, optimized ) }
到此,关于“Vue中的KeepAlive组件怎么使用”的学习就结束了,希望能够解决大家的疑惑。理论与实践的搭配能更好的帮助大家学习,快去试试吧!若想继续学习更多相关知识,请继续关注百云主机网站,小编会继续努力为大家带来更多实用的文章!
相关推荐: 怎么使用Vue3 SFC和TSX方式调用子组件中的函数
今天小编给大家分享一下怎么使用Vue3SFC和TSX方式调用子组件中的函数的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下吧。在开发中会遇到这样的需求…
免责声明:本站发布的图片视频文字,以转载和分享为主,文章观点不代表本站立场,本站不承担相关法律责任;如果涉及侵权请联系邮箱:360163164@qq.com举报,并提供相关证据,经查实将立刻删除涉嫌侵权内容。