宝哥软件园

Vue内部渲染方法

编辑:宝哥软件园 来源:互联网 时间:2021-08-20

1.什么是虚拟DOM

过去M的命令式操作DOM是用jQuery来操作DOM节点的。随着状态的增加,DOM操作会越来越频繁,程序的状态也会越来越难维护。目前主流框架采用声明式操作DOM,封装了操作DOM的方法。我们只需要改变数据的状态,框架本身会帮助我们操作DOM。虚拟DOM根据状态建立虚拟节点树。新的虚拟节点树将与旧的虚拟节点树进行比较,并且仅渲染改变的部分,如下图3360所示

2.引入虚拟DOM的目的

对渲染过程进行抽象,使得组件的抽象能力也得到提升,能够适应DOM以外的渲染目标;可以更好地支持SSR和同构渲染;不依赖HTML解析器进行模板解析,可以做更多的AOT(预编译)工作来提高运行时效率,并且可以进一步压缩Vue运行时卷。VNode的构造函数是在Vue中定义的,这样我们就可以实例化不同的VNode实例,比如文本节点、元素节点和注释节点。

var VNode=function VNode(标记、数据、子对象、文本、elm、上下文、componentOptions、async factory){ this . tag=tag;this.data=数据;this.children=儿童;this.text=textthis.elm=榆树;this.ns=未定义;this.context=contextthis.fnContext=undefinedthis.fnOptions=undefinedthis.fnScopeId=undefinedthis . key=data data . key;this.component options=component options;this.component instance=undefined;this.parent=undefinedthis.raw=falsethis.isStatic=falsethis.isRootInsert=truethis.isComment=falsethis.isCloned=falsethis.isOnce=falsethis . async factory=async factory;this.asyncMeta=undefinedthis.isAsyncPlaceholder=false};Vnode实际上是一个描述节点的对象,描述如何创建真实的DOM节点;vnode的功能是将新的vnode与旧的进行比较,并且只更新已更改的节点。VNode有注释节点、文本节点、元素节点、组件节点、功能组件和克隆节点:

注释节点

var createEmptyVNode=function(text){ if(text===void 0)text=' ';var node=new Vnode();node.text=textnode.isComment=true返回节点};默认情况下,只有isComment和text属性有效,其余属性为false或null

文本节点

函数create text vnode(val){ return new vnode(undefined,undefined,undefined,string (val))}只有一个文本属性

克隆节点

函数clonev node(VNode){ var cloned=new VNode(VNode . tag,vnode.data,//#7975 //克隆子级数组,以避免在克隆//子级时对原始数组进行变异。vnode . children vnode . children . slice()、vnode.text、vnode.elm、vnode.context、vnode.componentOptions、vnode . async factory);cloned . ns=vnode . ns;closed . IsStatic=vnode . IsStatic;closed . key=vnode . key;cloned . IsComment=vnode . IsComment;closed . fn context=vnode . fn context;closed . fnoptions=vnode . fnoptions;cloned . fnscopeid=vnode . fnscopeid;closed . async meta=vnode . async meta;cloned.isCloned=trueReturn cloned}克隆节点将vnode的所有属性分配给克隆节点,并将isCloned设置为true。其功能是优化静态节点和槽节点。以静态节点为例,由于静态节点的内容不会发生变化,在第一次生成虚拟DOM节点时,在再次更新时不需要再次生成vnode,而是克隆原始vnode的副本进行渲染,一定程度上提升了性能。

元素节点通常有四个有效属性,标签、数据、子节点和上下文,如:

{children: [vnode,vnode],context 3360 }.},tag:' div ',data : { attr 3360 { id : app } }组件节点有两个唯一的属性(1) componentOptions,组件节点的选项参数,包括以下内容33366

{ ctor : ctor,propsdata : propsdata,listeners : listeners,tag : tag,children : children} (2)组件实例:的一个实例,也是对应于Vue实例的vnode

新vnode((“vue-component-”)(ctor。cid()名称?('-' name) : ' ')),数据、未定义、未定义、未定义、上下文、Ctor: Ctor、propsData: propsData、listeners: listeners、tag:标记儿童工厂)即

{ componentOptions: {,组件实例: },标记: 'vue-component-1-child ',数据: { 0 }.},}函数式组件函数组件通过createFunctionalComponent函数创建,跟组件节点类似,暂时没看到特殊属性,有的话后续再补上。

修补

虚拟数字正射影像图最重要的功能是补丁,将虚拟节点渲染为真实的多姆。

修补简介

修补中文意思是打补丁,也就是在原有的基础上修改数字正射影像图节点,也可以说是渲染视图多姆。节点的修改有三种:

创建新增节点删除废弃的节点修改需要更新的节点。当缓存上一次的oldvnode与最新的虚拟节点不一致的时候,渲染视图以虚拟节点为准。

初次渲染过程

当oldvnode中不存在,而虚拟节点中存在时,就需要使用虚拟节点新生成真实的数字正射影像图节点并插入到视图中。首先如果虚拟节点具有标签属性,则认为它是元素属性,再根据当前环境创建真实的元素节点,元素创建后将它插入到指定的父节点。以上节生成的虚拟节点为例,首次执行

虚拟机更新(虚拟机._render(),补水);虚拟机。_渲染()为上篇生成的VNode,_update函数具体为

vue。原型。_ update=function(vnode,补水){ var vm=thisvar prevEl=vm .$ elvar prevVnode=vm ._ vnodevar还原实例=setActiveInstance(VM);//缓存虚拟节点虚拟机_ vnode=vnode//Vue . prototype . _ _ patch _ _是根据使用的渲染后端在入口点//注入的。//第一次渲染,preVnode是不存在的if(!上一个节点){//初始渲染vm .$el=vm .__补丁_ _(虚拟机$el,vnode,补水,false/* removeOnly */);} else { //更新vm .$el=vm ._ _ patch _ _(prev节点,vnode);}还原活动实例();//更新_ _ vue _ _参考if(previel){ previel ._ _ vue _ _=null} if (vm .$el) { vm .$el ._ _ vue _ _=vm} //如果父级是HOC,也更新它的$ El if(VM .$vnode虚拟机。$父虚拟机$vnode===vm .$家长_vnode) { vm .$家长$el=vm .$ el} //更新了挂钩由调度程序调用,以确保子级在父级的更新钩中被//更新。};因第一次渲染,执行虚拟机$el=vm .__补丁_ _(虚拟机$el,vnode,补水,false/* removeOnly */);注意第一个参数是旧代码为虚拟机$el .为元素节点,__patch__函数具体过程为:

(1) 先判断旧代码是否存在,不存在就创建虚拟节点

如果(isUndef(oldVnode)) { //空挂载(可能作为组件),则创建新的根元素is initialpatch=TrueCreateElm(vnode,insertedvnode队列);}(2) 存在进入否则,判断旧代码是否是元素节点,如果旧代码是元素节点,则

if(IsRealeElement){ 0.//不是服务器渲染,就是水合失败。//创建一个空节点,替换为oldVnode=emptyNodeAt(oldVnode);}创建一个旧代码节点,其形式为

{不对称因子:未定义,不对称元:未定义,子系: [],组件实例:未定义,组件选项3360未定义,上下文:未定义,数据: {},elm: div#app,fnContext:未定义,fnOptions:未定义,fnScopeId:未定义,isAsyncPlaceholder:为假,isCloned:为假,isComment:然后获取旧代码的元素节点以及其父节点,并创建新的节点

//替换现有的element var oldElm=oldvnode . elm var parent elm=node ops。父节点(OLDELM);//创建新节点createElm(vnode,insertedVnodeQueue,//极少数边缘情况:如果旧元素处于//离开转换中,则不要插入。仅在组合transition//keep-alive HoC时发生#4590) oldElm ._leaveCb?null : parentElm,nodeops。next sibling(OLDELM));创建新节点的过程

//标记是否是根节点vnode.isRootInsert=!嵌套的;//对于过渡,输入检查/这个函数如果虚拟节点有件实例属性,会创建子组件,后续具体介绍,否则不做处理if (createComponent(vnode,insertedVnodeQueue,parentElm,refElm)) { return}接着在对子节点处理

var data=vnode . datavar children=vnode . children var标记=vnode.tagif(IsDef(标记)){ 0.vnode.elm=vnode.ns?节点操作。createelements(vnode。ns,标记): nodeOps.createElement(标记,vnode);setScope(vnode);/*伊斯坦布尔忽略if */{ createChildren(vnode,Children,insertedvnode队列);if(IsDef(data)){ invokeCreateHooks(vnode,insertedvnode队列);} insert(parentElm,vnode.elm,RefElm);} if(数据数据。pre){ createngelminvpre-;} }}将虚拟节点的属性设置为创建元素节点elem,创建子节点createChildren(vnode,Children,insertedvnode队列);该函数遍历子节点孩子们数组

函数createChildren (vnode,Children,insertedvnode队列){ if(数组。isarray(children)){ for(var I=0;一、儿童i) { createElm(children[i],insertedVnodeQueue,vnode.elm,null,true,children,I);} } else if()是基元(vnode。文本)){//如果虚拟节点是文本直接挂载nodeOps.appendChild(vnode.elm,nodeOps。createtextnode(String(vnode。文本)));}}遍历孩子们,递归创建榆树方法创建子元素节点

else if()为TRue(vnode。IsComment)){ vnode。elm=节点操作。创建注释(vnode。文本);插入(parentElm、vnode.elm、RefElm);} else { vnode。elm=NodeOps。CreateTextNode(vnode。文本);插入(parentElm、vnode.elm、RefElm);}如果是评论节点,直接创建评论节点,并将其插入到父节点上,其他的创建文本节点,并将其插入到父节点parentElm(刚创建的div)上去。触发钩子,更新节点属性,将其插入到parentElm('#app '元素节点)上

{ createChildren(vnode,Children,insertedvnode队列);if(IsDef(data)){ invokeCreateHooks(vnode,insertedvnode队列);} insert(parentElm,vnode.elm,RefElm);}最后将老的节点删掉

if(IsDef(ParentElm)){ removeVnodes(ParentElm,[oldVnode],0,0);} else if(isDef(oldVnode。标记)){ invokeDestroyHook(oldVnode);}函数removeandivokeremovehook(vnode,RM){ if(IsDef(RM)| | IsDef(vnode。数据)){ var I;var listeners=CBS。移除。长度1;//递归调用子组件根节点上的钩子if(IsDef(I=v节点。ComponentInstance)IsDef(I=I . v节点)IsDef(I . data)){ removeAnDivokeremoveook(I,RM);} for(I=0;我是哥伦比亚广播公司的。移除。长度;i) { cbs.remove[i](vnode,RM);} if(IsDef(I=vnode。数据。hook)IsDef(I=I . remove)){ I(vnode,RM);} else { //删除编号为应用的老节点RM();} } else { remove node(vnode。榆树);}}初次渲染结束。

更新节点过程

为了更好地测试,模板选用

div id=' app ' { { message } }按钮@单击='更新'更新/button/div点击按钮,会更新消息,重新渲染视图,生成的虚拟节点为

{不对称系数:未定义,asyncMeta:未定义,children: [VNode,VNode],componentInstance:未定义,组件选项3360未定义,context: Vue实例,数据: { attrs : { id : ' app ' } },elm:未定义,fnContext:未定义,fnOptions:未定义,fnScopeId:未定义,isAsyncPlaceholder:假,isCloned:假,isComment:假,isOnce:假,isRootInsert:真,isStatic:假,key 3333330假在组件更新的时候,preVnode和虚拟节点都是存在的,执行

vm,$ El=VM,——第_ _ _ _号补丁(预发本,vnode);是吕惠卿还是吕惠卿

补丁程序节点(oldVnode、Vnode、insertedVnodeQueue、null、null、仅移除);范思哲范思哲范思哲范思哲、范思哲范思哲范思哲范思哲

如果(oldVnode===vnode) { return}是基督山,则是基督山隔离岛,基督山组件

if(ist代码。isstatic)ist code(旧vnode。isstatic)vnode。key===旧vnode。密钥(ist代码。isclond)| | ist代码(vnode。isonce)){ vnode。组件实例=旧vnode。组件实例;return }

var oldch=oldvnode。childrenvar ch=vnode。children if(isdef(data)is patchable(vnode)){ for(I=0;哥伦比亚广播公司。更新。长度;哥伦比亚广播公司。更新[I](旧vnode,vnode);} if(isdef(I=数据。hook)isdef(I=I . update)){ I(旧vnode,vnode);} } vnoded vnoded jumbardo jumbardo mucho?3360 .你的意思是,你的意思是,你的意思是,你的意思是,你的意思是,你的意思是,你的意思是,你的意思是,你的意思是,你的意思是,你的意思是,你的意思是,你的意思是,你的意思是,你的意思是,你的意思是你的意思

//vnoded(vnoded。正文)){ if(isdef(old ch)isdef(ch)){//Yankee,sichung if(old ch!不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不,不==ch){ updatechilders(elm,oldCh,ch,insertedVnodeQueue,removeonly);} } else if(isdef(ch)){ {检查重复键(ch);}//youvnodensudong(旧代码。文本){无操作。settext内容(elm,');} addvnnodes(elm,null,ch,0,ch.length - 1,insertedvnode queue);} else if(isdef(oldCh)){//old vnoded zhong ing,ooh womenchung remove vnnodes(elm,oldCh,0,old ch。长度-1);} else if(isdef(旧vnode。text)){//旧的vnoded text,be hronoving no ops。settext内容(elm,');}} else if (oldVnode.text!==vnode。文本){//没有旧的vnoded文本<文本名称>,<文本名称> .禁止操作。settext内容(elm,vnode。文本)} <文本名称> } <文本名称>,<文本名称>

var旧起始idx=0;var news start idx=0;var oldenddx=old ch。长度-1;var old start vnode=old ch[0];var oldendvnode=old ch[oldenddx];var newenddx=newch。长度-1;var newstartvnode=new ch[0];var new endvnode=new ch[new enddx];var oldkeytoidx、idxInOld、vnodeToMove、refelm三零六

范思哲范思哲、范思哲、奥尔德斯塔西纽恩迪兹范思哲

而(旧开始idx=oldenddx news startidx=new enddx)旧开始vnodevnodevicendvnodensodeun(旧启动Idx=newEndIdx)则是指

if(旧起始vnode)){旧起始vnode=旧ch[旧起始idx];//vnode已左移} else if(is UNDAF(oldendvnode)){ oldendvnode=old ch[-oldenddx];}韩升洙、陈光诚、陈光诚、'新闻起点索引'、'新闻起点索引'、'新闻起点索引'、'新闻起点索引'、'新闻起点索引'、'新闻起点索引'、'新闻起点索引'、'新闻起点索引'、'新闻起点索引'、'新闻终点索引'、'新闻起点索引'、'新闻起点索引'、'新闻起点索引'、'新闻起点索引'、'新闻起点索引'、'新闻起点索引'、'新闻起点索引'、'新闻起点索引'、'新闻起点索引'、'新闻起点索引'

else if(samvnode(oldStartVnode,newStartVnode)){ 0修补程序节点(旧startvnode、newStartVnode、insertedVnodeQueue、newCh、newstartidx);旧开始vnode=旧ch[旧开始idx];news tart vnode=newch[news tart idx];} else if(samvnode(oldEndVnode,new endvnode)){ 0修补程序节点(oldendvnode、newEndVnode、insertedVnodeQueue、newCh、new enddx);oldendvnode=old ch[-oldenddx];new endvnode=new ch[-new enddx];} else if(同一个Vnode(旧起始vnode,newEndVnode)){//vnode右移补丁节点(旧起始vnode,newEndVnode,insertedVnodeQueue,newCh,new enddx);可以移动节点操作。在(父elm、oldStartVnode.elm、nodeops)之前插入。nextsibling(oldendvnode。elm));旧开始vnode=旧ch[旧开始idx];new endvnode=new ch[-new enddx];} else if(samvnode(oldendvnode,newStartVnode)){//vnode左移patches节点(oldendvnode,newStartVnode,insertedVnodeQueue,newCh,newstartidx);可以移动节点操作。在(父elm,oldEndVnode.elm,old startvnode)之前插入。榆树);oldendvnode=old ch[-oldenddx];news tart vnode=newch[news tart idx];} 360度角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点角点

如果它们相等,则oldStartVnode.elm和newStartVnode.elm都向后移动一位以继续比较。第二次:后平等比较

如果它们相等,oldEndVnode.elm和newEndVnode.elm都向前移动一位以继续比较。第三个:前后相等比较

将oldStartVnode.elm节点直接移到oldEndVnode.elm节点后面,然后将oldStartIdx向后移动一位,将newEndIdx向前移动一位。第四个比较:前后相等

将oldEndVnode.elm节点直接移到oldStartVnode.elm节点的后面,然后将oldEndIdx向前移动一位,将newStartIdx向后移动一位。如果以上都不满足,那么

else { if(isUndef(oldKeyToIdx)){ oldKeyToIdx=createKeyToOldIdx(oldCh,oldStartIdx,oldEndIdx);} idxInOld=isDef(newstartvnode . key)?oldKeyToIdx[newStartVnode . key]: finidxinod(newStartVnode,oldCh,oldStartIdx,oldEndIdx);if(isUndef(idxinod)){//New element createElm(newStartVnode,insertedVnodeQueue,parentElm,oldStartVnode.elm,false,newCh,newStartIdx);} else { vnodeToMove=old ch[idxinoold];if(same vnode(vnodetoove,newStartVnode)){ patchVnode(vnodetoove,newStartVnode,insertedVnodeQueue,newCh,newStartIdx);oldCh[idxinod]=未定义;can move nodeops . insertbefore(parentElm,vnodeToMove.elm,oldstartvnode . elm);} else { //相同的键但不同的元素。作为新元素对待createElm(newStartVnode,insertedVnodeQueue,parentElm,oldStartVnode.elm,false,newCh,newStartIdx);} } NewStartVnode=NewCh[NewStartIdx];}createkeyToOldIdx函数用于创建与键和索引索引对应的映射表,如果没有找到节点,则创建一个新节点

createElm(newStartVnode,insertedVnodeQueue,parentElm,oldStartVnode.elm,false,newCh,newStartIdx);将其插入oldStartVnode.elm前面,否则,如果找到该节点并符合sameVnode,则修补两个节点Vnode,将此位置的旧节点设置为未定义,将vnodeToMove.elm移动到Oldstartvnode.elm前面,并将newStartIdx移回一个位置,如下图所示:

如果不满足sameVnode,则只能创建一个新节点并将其插入到parentElm的子节点中,并且newStartIdx将向后移动一位。最后,如果oldStartIdx oldEndIdx表示旧节点已经比较过了,但是新节点还是很多,那么需要将新节点插入到真实的DOM中,调用addVnodes来插入这些节点;如果满足newStartIdx newEndIdx条件,则表示新节点的比较完成,旧节点较多。您可以通过removeVnodes批量删除这些无用的旧节点。这个过程基本到此结束。

摘要

以上就是边肖介绍的Vue里面渲染视图的方法,对大家有帮助。如果你有任何问题,请给我留言,边肖会及时回复你。非常感谢您对我们网站的支持!如果你觉得这篇文章对你有帮助,请转载,请注明出处,谢谢!

更多资讯
游戏推荐
更多+