彻底揭示保活原理(综述)
一、前言
原始链接:https://github.com/qiudongwei/blog/issues/4
本文介绍的内容包括:
Keep-alive用法:动态组件vue-router keep-alive源代码分析keep-alive组件及其包装组件挂接keep-alive组件及其包装组件渲染II。保活技术的介绍及应用
2.1什么是保活
Keep-alive是一个抽象组件:它不会自己呈现一个DOM元素,也不会出现在父组件链中;当使用保活来包装动态组件时,非活动组件实例将被缓存而不是销毁。
2.2场景
用户在列表页面上选择过滤条件以过滤出数据列表,然后从列表页面进入数据详细信息页面并返回列表页面。我们希望列表页面能够保持用户的过滤(或选择)状态。保活就是用来解决这个场景的。当然,保活不仅能够保存页面/组件的状态,还可以避免组件的重复创建和渲染,有效提高系统性能。一般来说,保活用于保存组件的渲染状态。
2.3动态组件应用中的保活使用
保活:的应用包括='白名单' :排除='黑名单' :最大值='金额'组件:是='当前组件'/vue-router中的组件/保活
保活: include='白名单' : exclude='黑名单' : max='金额'路由器-视图/路由器-视图/保活include定义了缓存白名单,保活将缓存命中的组件。Exclude定义了缓存黑名单,命中的组件不会被缓存;Max定义了缓存组件的上限。如果超过上限,缓存数据将被LRU的策略替换。
三、源代码分析
在keep-alive.js中还定义了一些工具函数。我们按住这个表,先看看它暴露的对象。
//src/core/components/keep-alive . js export默认{ name: 'keep-alive ',abstract: true,//判断当前组件的虚拟dom是否呈现为key props: { includ:模式类型,//缓存白名单exclud:模式类型,//缓存黑名单max: [String,Number] //最大缓存组件实例数},created () {this。cache=对象。create(null)//cache virtual DOM this . keys=[]//cache virtual DOM密钥集},desced(){ for(const key in this。缓存){//删除所有缓存prunecacheentry(这。缓存,钥匙,这个。按键)}},挂载(){//实时监控黑白列表的变化这。$ watch ('include ',val={ pre cache(This,name=matches (val,name))}) This。$ watch ('exclude ',val={ pre cache(this,name=!匹配(val,name))}},render(){//先忽略.}}可以看到,和定义组件的过程一样,我们首先将组件的名称设置为keep-alive,然后定义一个值为true的抽象属性。vue的官方教程中没有提到这个属性,但它非常重要,将在后续的渲染过程中使用。props属性定义了保活组件支持的所有参数。
保活在其生命周期中定义了三个钩子函数:
创造
初始化两个对象,分别缓存虚拟DOM和虚拟DOM对应的密钥集
破坏
删除缓存在this.cache中的VNode实例我们注意到,我们没有简单地将this.cache缓存为null,而是调用了pruneCacheEntry函数来删除它。
//src/core/components/keep-alive . jsfunction Pronecacheentry(cache : Vnodecache,key: string,keys: Arraystring,current? VNode){ const cached=cache[key]if(cached(!current | | cached.tag!==当前。标记)){缓存。组件实例。$ destory()//执行组件的doom hook函数}缓存[key]=null remove(key,key)}删除缓存VNode并相应地执行组件实例的doom hook函数
安装好的
监视挂接中的包含和排除参数,然后实时更新(删除)this.cache对象的数据。夏枯草缓存函数的核心是调用夏枯草缓存条目。
提出
//src/core/components/keep-alive . js render(){ const slot=this。$ slots . default const vnode 3360 vnode=getfirst component child(slot)//查找第一个子组件对象const componentOptions:如果(component options){//有组件参数//请检查模式常量名称:string=getcomponent name(component options)//component name const { include,exclude }=this if(//条件匹配//不包括(include(!名称||!匹配(包括,名称)))|| //排除(排除名称匹配(排除,名称))){返回vnode } const { cache,keys }=这个const key:String=vnode.key==null //定义组件的缓存键//相同的构造函数可能会注册为不同的本地组件//所以仅cid是不够的(# 3269)?组件选项。Ctor.cid (componentOptions.tag?` :3360 $ {componentoptions。标记} ` :'') : vnode。key if(缓存[key]){//组件vnode。componentinstance=cache [key]。componentinstance//使当前键最新鲜移除(key,Key)键. push(key) //调整键排序} else {cache[key]=vnode //缓存组件对象键。push(key)//如果(this . maxkeys . length parsent(this . max))超过缓存限制,则修剪最旧的条目{//如果超过缓存限制,则修剪缓存条目(cache,keys [0],keys,this。_ vnode)} } vnode . data . keepalive=true//呈现和执行包装组件的钩子函数的第一步是}返回vnode || (slot slot[0])
第二步:根据设定的黑白名单(如有)匹配条件,决定是否缓存。如果不匹配,直接返回组件实例(VNode),否则执行第三步;
第三步:根据组件ID和标签生成缓存Key,找出组件实例是否已经缓存在缓存对象中。如果存在,直接取出缓存值,更新key . keys中的key的位置(更新key的位置是实现LRU替换策略的关键),否则,执行第四步;
第四步:将组件实例存储在this.cache对象中并保存键值,然后检查缓存的实例数量是否超过max的设置值,并根据LRU替换策略删除最新未使用的实例(即下标为0的键)。
第5步:最后也是最重要的一点,将这个组件实例的keepAlive属性值设置为true。这在@:中不能忽略,钩子函数部分将再次出现。
第四,亮点:渲染
4.1渲染过程
拍张图看看Vue渲染的全过程:
Vue的渲染从图中的渲染阶段开始,但是keep-alive的渲染是在补丁阶段,这是构建组件树(虚拟DOM树)并将VNode转换为真实DOM节点的过程。
简要描述从渲染到修补的过程
让我们从最简单的新Vue开始:
从“”导入应用程序。/app . vue ' new vue({ render :h=h(app),})。$ mount(“# app”)vue调用原型上的_render函数,将组件对象转换为VNode实例;And _render通过调用createElement和createEmptyVNode进行转换。在createElement的转换过程中,会根据不同情况选择新的VNode,或者调用createComponent函数实例化VNode;实例化VNode后,Vue调用原型上的_update函数,将VNode渲染为真实的DOM,这个过程是通过调用__patch__函数完成的(这是pacth阶段),用图片表示:
4.2保活组件的渲染
众所周知,当我们使用保活时,它不能生成真正的DOM节点。这是怎么做到的?
//src/core/instance/生命周期。jsexport函数初始化生命周期(虚拟机:组件){常量选项=虚拟机.$options //找到第一个非摘要的父组件实例让parent=options.parent if (parent!options.abstract) { while (parent .$选项。抽象父级$parent) { parent=parent .$parent } parent .$children.push(vm) } vm .$parent=parent //.}Vue在初始化生命周期的时候,为组件实例建立父子关系会根据摘要属性决定是否忽略某个组件。在点火电极中,设置了抽象:真的,那某视频剪辑软件就会跳过该组件实例。
点火电极包裹的组件是如何使用缓存的?
在修补阶段,会执行createComponent函数:
//src/core/vdom/patch。jsfunction创建组件(v节点,insertedVnodeQueue,parentElm,ref elm){ let I=v节点。数据if(IsDef(I)){ const IsReactive=IsDef(v节点。组件实例)I . keepalive if(IsDef(I=I . hook)IsDef(I=I . init)){ I(v节点,false /*补水*/)} if(IsDef(v)节点。组件实例){ init组件(v节点,insertedvnode queue)insert(parent e)将缓存的DOM(vnode.elm)插入父元素中if(IsRue(IsReactivated)){重新激活的组件(vnode,insertedVnodeQueue,parentElm,refElm) }返回true } } }在首次加载被包裹组件时,由keep-alive.js中的提出函数可知,vnode.componentInstance的值是未定义,保持活动的值是没错,因为点火电极组件作为父组件,它的提出函数会先于被包裹组件执行;那么就只执行到i(vnode,false /*补水*/),后面的逻辑不再执行;
再次访问被包裹组件时,vnode.componentInstance的值就是已经缓存的组件实例,那么会执行插入(parentElm、vnode.elm、refElm)逻辑,这样就直接把上一次的数字正射影像图插入到了父元素中。
五、不可忽视:钩子函数
5.1 只执行一次的钩子
一般的组件,每一次加载都会有完整的生命周期,即生命周期里面对应的钩子函数都会被触发,为什么被点火电极包裹的组件却不是呢?我们在@源码剖析章节分析到,被缓存的组件实例会为其设置keepAlive=true,而在初始化组件钩子函数中:
//src/core/vdom/create-component。jsconst组件vnode hooks={ init(vnode : vnode with data,hydrating: boolean):布尔{ if (vnode.componentInstance!vnode。组件实例。_被销毁的vnode。数据。keepalive){//keepalive-alive组件,将任何=vnode //的修补程序const mountedNode视为工作流组件vnode挂钩。prepatch(已装载节点,已装载节点)} else { const child=vnode。组件实例=createcomponentsinstancefronode(vnode,activeInstance)子级.$mount(补水?vnode.elm :未定义,补水)} } //.}可以看出,当vnode.componentInstance和保持活力同时为真实地;诚挚地;准确地值时,不再进入$mount过程,那安装好的之前的所有钩子函数(在创建、创建、安装之前)都不再执行。
5.2 可重复的激活的
在修补的阶段,最后会执行invokeInsertHook函数,而这个函数就是去调用组件实例(VNode)自身的插入钩子:
//src/core/vdom/patch.js函数invokeinserbok(vnode,queue,initial){ if(iStrue(initial)IsDef(vnode。parent)){ vnode。父母。数据。挂起的插入=queue } else { for(让I=0;队列长度;我){队列[我]。数据。钩子。插入(队列[I])//调用虚拟节点自身的插入钩子函数} } }再看插入钩子:
//src/core/vdom/create-component。jsconst组件vnode hooks={//init())insert(vnode : mounteddcomponent vnode){ const { context,componentInstance }=vnode if(!组件实例._isMounted) { componentInstance ._ IsMounted=true callHook(ComponentInstance,‘mounted’)} if(vnode。数据。keepalive){ if(context ._ IsMounted){ queueActivated component(组件实例)} else { activatedchildcomponent(组件实例,true/* direct */)}//.}在这个钩子里面,调用了激活儿童组件函数递归地去执行所有子组件的激活的钩子函数:
//src/core/instance/生命周期。jsexport函数激活儿童组件(虚拟机:组件,直接?布尔值){ if (direct) { vm ._ directionactivity=false if(Isinactivitree(VM)){ return } } else if(VM ._ directionactivity){ return } if(VM ._非活动||虚拟机_inactive===null) { vm ._inactive=false(让I=0;我虚拟机$ children . lenti){ activateChildComponent(VM).$children[i]) } callHook(vm,‘activated’)} }相反地,已停用钩子函数也是一样的原理,在组件实例(VNode)的破坏钩子函数中调用停用子组件函数。
参考
某视频剪辑软件技术揭秘|保持活力
某视频剪辑软件源码
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持我们。
版权声明:彻底揭示保活原理(综述)是由宝哥软件园云端程序自动收集整理而来。如果本文侵犯了你的权益,请联系本站底部QQ或者邮箱删除。