Vue底层原理之虚拟DOM(2)节点更新流程
更新节点的流程:
先完成不是同⼀个节点的情况:
patch.js:
import vnode from'./vnode'
import createElement from'./createElement'
export default function(oldVnode,newVnode){
// 判断传⼊的第⼀个参数是DOM节点还是虚拟节点
if(oldVnode.sel==''||oldVnode.sel==undefined){//没有sel属性就是DOM节点
oldVnode=vnode(LowerCase(),{},[],undefined,oldVnode);//DOM节点包装成虚拟节点
}
// 判断oldVnode和newVnode是不是同⼀个节点
if(oldVnode.sel==newVnode.sel&&oldVnode.key==newVnode.key){
// 同⼀个节点
}else{
// 不是同⼀个节点,暴⼒删除旧的,添加新的
createElement(newVnode,oldVnode.elm)
}
}
createElement.js:
// 真正创建节点,将vnode创建为DOM,插⼊到pivot这个元素之前
export default function(vnode,pivot){
let ateElement(vnode.sel);
// 有⼦节点还是有⽂本
!=''&&(vnode.children==undefined||vnode.children.length==0)){
vuejs流程图插件// 内部是⽂本
domNode.;
// 节点上树标杆节点的⽗元素调⽤insertBefore⽅法
pivot.parentNode.insertBefore(domNode,pivot);
}else if(Array.isArray(vnode.children)&&vnode.children.length>0){
}
}
在创建DOM元素的时候,需要判断该节点有没有⼦节点,没有就直接设置好⽂本,让节点上树。该节点需要有⼀个标杆节点,这个标杆就是原来旧节点对应的DOM,我们要在标杆之前插⼊新节点。
⽽如果新的节点有⼦节点,就需要递归调⽤createElement函数,当⼦节点再没有⼦节点,只有⽂本时,递归结束。但是,这⾥的⼦节点是没有标杆节点(pivot)的,所以我们需要修改⼀下函数。
// 真正创建节点,将vnode创建为DOM
export default function createElement(vnode){
let ateElement(vnode.sel);
// 有⼦节点还是有⽂本
!=''&&(vnode.children==undefined||vnode.children.length==0)){
// 内部是⽂本
domNode.;
}else if(Array.isArray(vnode.children)&&vnode.children.length>0){
// 内部有⼦节点递归创建⼦节点
for(let i=0;i<vnode.children.length;i++){
let ch=vnode.children[i];
let chDOM=createElement(ch);// ⼀旦调⽤createElement就意味着创建出DOM了,并且它的elm属性指向了创建出的DOM,但是还没有上树
domNode.appendChild(chDOM);//真正的DOM
}
}
// 补充elm属性
vnode.elm=domNode
return vnode.elm;
}
export default function(oldVnode,newVnode){
// 判断传⼊的第⼀个参数是DOM节点还是虚拟节点
if(oldVnode.sel==''||oldVnode.sel==undefined){//没有sel属性就是DOM节点
oldVnode=vnode(LowerCase(),{},[],undefined,oldVnode);//DOM节点包装成虚拟节点
}
// 判断oldVnode和newVnode是不是同⼀个节点
if(oldVnode.sel==newVnode.sel&&oldVnode.key==newVnode.key){
// 同⼀个节点
}else{
// 不是同⼀个节点,暴⼒删除旧的,添加新的
let newVnodeElm=createElement(newVnode);
// 插⼊到旧节点之前
if(oldVnode.elm.parentNode!=undefined&&newVnodeElm){
oldVnode.elm.parentNode.insertBefore(newVnodeElm,oldVnode.elm)
}
// 删除旧节点
oldVnode.veChild(oldVnode.elm)
}
}
此时,如果:
const vnode1=h('h1',{},'你好');
const vnode2=h('ul',{},[
h('li',{},'111'),
h('li',{},'111'),
h('li',{},'111')
]
)
patch(vnode1,vnode2);
页⾯就只会显⽰vnode2。到这⾥,我们实现了两个虚拟节点不是同⼀个的情形。此时还没有使⽤到diff算法。
接下来,需要实现两个虚拟节点是同⼀个节点的情况。续上上边的流程图:
同⼀个对象指的是const vnode2=vnode1,显然,不需要操作什么。流程图中,五⾓星位置的情况最为复杂,就是新旧虚拟节点都有children,是需要递归的。我们先来实现⼀下五⾓星之外的情形。
export default function(oldVnode, newVnode){
// 判断传⼊的第⼀个参数是DOM节点还是虚拟节点
if(oldVnode.sel ==''|| oldVnode.sel == undefined){//没有sel属性就是DOM节点
oldVnode =vnode(LowerCase(),{},[], undefined, oldVnode);//DOM节点包装成虚拟节点
}
// 判断oldVnode和newVnode是不是同⼀个节点
if(oldVnode.sel == newVnode.sel && oldVnode.key == newVnode.key){
// 同⼀个节点
// 判断是否是同⼀个对象
if(oldVnode === newVnode)return;// 同⼀个对象直接返回
// 判断新vnode有没有text属性
!= undefined &&(newVnode.children == undefined || newVnode.children.length ==0)){// 有text
=== ){//新旧text是否相同
return;
}else{
// 旧节点的⽂本替换为新节点的⽂本,oldVnode是虚拟节点,它不能点出innerText,只能oldVnode.elm.innerText
// newVnode也是虚拟属性,并且这⾥它还没有elm,所以要⽤
// 即便旧节点中有children,改了innerText,children也就没了
oldVnode.elm.innerText = ;
}
}else{// 新的没有text属性意味着新的有children
// 判断旧的有没有children
if(oldVnode.children == undefined || oldVnode.children.length ===0){// 旧的没有children,意味着旧的有text属性
// 清空text
oldVnode.elm.innerHTML =''
// 把新的children添加进来
for(let i =0; i < newVnode.children.length; i++){
let ch = newVnode.children[i];
let chDOM =createElement(ch);
oldVnode.elm.appendChild(chDOM)
}
}
}
}else{
// 不是同⼀个节点,暴⼒删除旧的,添加新的
let newVnodeElm =createElement(newVnode);
// 插⼊到旧节点之前
if(oldVnode.elm.parentNode != undefined && newVnodeElm){
oldVnode.elm.parentNode.insertBefore(newVnodeElm, oldVnode.elm)
}
// 删除旧节点
oldVnode.veChild(oldVnode.elm)
}
}
当两个虚拟节点都有children时,由于需要递归调⽤函数,我们需要把部分代码抽离出来,建⽴patchVnode.js⽂件:
import createElement from'./createElement'
export default function patchVnode(oldVnode,newVnode){
// 判断是否是同⼀个对象
if(oldVnode === newVnode)return;// 同⼀个对象直接返回
// 判断新vnode有没有text属性
!= undefined &&(newVnode.children == undefined || newVnode.children.length ==0)){// 有text
=== ){//新旧text是否相同
return;
}else{
// 旧节点的⽂本替换为新节点的⽂本,oldVnode是虚拟节点,它不能点出innerText,只能oldVnode.elm.innerText
// newVnode也是虚拟属性,并且这⾥它还没有elm,所以要⽤
// 即便旧节点中有children,改了innerText,children也就没了
oldVnode.elm.innerText = ;
}
}else{// 新的没有text属性意味着新的有children
// 判断旧的有没有children
if(oldVnode.children == undefined || oldVnode.children.length ===0){// 旧的没有children,意味着旧的有text属性
// 清空text
oldVnode.elm.innerHTML =''
// 把新的children添加进来
for(let i =0; i < newVnode.children.length; i++){
let ch = newVnode.children[i];
let chDOM =createElement(ch);
oldVnode.elm.appendChild(chDOM)
}
}
}
}
这是从patch.js中抽离出来的。原来的patch中这部分代码替换为patchVnode(oldVnode,newVnode);即可。
先在vnode.js⾥,把key属性加上:
export default function(sel,data,children,text,elm){
const key=data.key;
return{
sel,data,children,text,elm,key
}
}
两个都有children⼜可分为⼏种情况:新增,删除,修改。。
如果是新增的话,遍历新节点的⼦节点,如果发现某个⼦节点在原来的节点中不存在,则为新增,它要插⼊的位置为所有未处理的节点之前,这不等同于所有已处理节点之后。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论