更新节点的流程:
先完成不是同一个节点的情况:
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(oldVnode.tagName.toLowerCase(),{},[],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 domNode=document.createElement(vnode.sel);
// 有子节点还是有文本
if(vnode.text!=''&&(vnode.children==undefined||vnode.children.length==0)){
// 内部是文本
domNode.innerText=vnode.text;
// 节点上树 标杆节点的父元素调用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 domNode=document.createElement(vnode.sel);
// 有子节点还是有文本
if(vnode.text!=''&&(vnode.children==undefined||vnode.children.length==0)){
// 内部是文本
domNode.innerText=vnode.text;
}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(oldVnode.tagName.toLowerCase(),{},[],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.elm.parentNode.removeChild(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(oldVnode.tagName.toLowerCase(), {}, [], undefined, oldVnode);//DOM节点包装成虚拟节点
}
// 判断oldVnode和newVnode是不是同一个节点
if (oldVnode.sel == newVnode.sel && oldVnode.key == newVnode.key) {
// 同一个节点
// 判断是否是同一个对象
if (oldVnode === newVnode) return;// 同一个对象直接返回
// 判断新vnode有没有text属性
if (newVnode.text != undefined && (newVnode.children == undefined || newVnode.children.length == 0)) {// 有text
if (newVnode.text === oldVnode.text) {//新旧text是否相同
return;
} else {
// 旧节点的文本替换为新节点的文本,oldVnode是虚拟节点,它不能点出innerText,只能oldVnode.elm.innerText
// newVnode也是虚拟属性,并且这里它还没有elm,所以要用newVnode.text
// 即便旧节点中有children,改了innerText,children也就没了
oldVnode.elm.innerText = newVnode.text;
}
} 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.elm.parentNode.removeChild(oldVnode.elm)
}
}
当两个虚拟节点都有children时,由于需要递归调用函数,我们需要把部分代码抽离出来,建立patchVnode.js文件:
import createElement from './createElement'
export default function patchVnode(oldVnode,newVnode){
// 判断是否是同一个对象
if (oldVnode === newVnode) return;// 同一个对象直接返回
// 判断新vnode有没有text属性
if (newVnode.text != undefined && (newVnode.children == undefined || newVnode.children.length == 0)) {// 有text
if (newVnode.text === oldVnode.text) {//新旧text是否相同
return;
} else {
// 旧节点的文本替换为新节点的文本,oldVnode是虚拟节点,它不能点出innerText,只能oldVnode.elm.innerText
// newVnode也是虚拟属性,并且这里它还没有elm,所以要用newVnode.text
// 即便旧节点中有children,改了innerText,children也就没了
oldVnode.elm.innerText = newVnode.text;
}
} 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又可分为几种情况:新增,删除,修改。。
如果是新增的话,遍历新节点的子节点,如果发现某个子节点在原来的节点中不存在,则为新增,它要插入的位置为所有未处理的节点之前,这不等同于所有已处理节点之后。
else{//新旧都有children
// 所有未处理节点的开头
let un=0;
for(let i=0;i<newVnode.children.length;i++){
let newCh=newVnode.children[i];
// 看oldVnode中有没有节点和它相同
let isExist=false;
for(let j=0;j<oldVnode.children.length;j++){
let oldCh=oldVnode.children[j];
if(oldCh.sel==newCh.sel&&oldCh.key==newCh.key){// 相同节点
isExist=true;
// 找到相同的,
un++;
break;
}
}
if(!isExist){//新节点的某个子节点旧的没有
console.log(i)
let dom=createElement(newCh);
newCh.elm=dom;
if(un<oldVnode.children.length){
oldVnode.elm.insertBefore(dom,oldVnode.children[un].elm);
}else{
oldVnode.elm.appendChild(dom);
}
}
}
}
un指向所有未处理节点的开头,如果新节点的子节点在旧节点中能够找到,则un下移,一直到某个子节点为新增节点时,将该子节点插入到un指向节点之前即可。
但是,这种做法很繁琐,我们只是处理了新增节点的情况,假设C变成了M,或者B删掉,诸如此类情况都要再加判断,一一处理。
到这里,就要引出diff算法了。
380

被折叠的 条评论
为什么被折叠?



