virtual dom 原理实现

  • 创建dom树
  • 树的diff,同层对比,输出patches(listDiff/diffChilder/diffProps)
  • 没有新节点,返回
  • 新的节点tagName与key不变,对比props,继续递归遍历子树
  • 对比属性(对比新旧属性列表)
  • 旧属性是否存在与新属性列表中
  • 都存在的是否有变化
  • 是否出现旧列表中没有的新属性
  • tagName和key值变化了,则直接替换成新节点
  • 渲染差异
  • 遍历patches,把需要更改的节点取出来
  • 局部更新dom
function diff(oldTree,newTree){
//差异收集
let patches ={};
dfs(oldTree,newTree,0,patches);
return patches;
}

function dfs(oldNode,newNode,index,patches){
let curPatches = [];
if(newNode){
//当前旧节点tagname和key值完全一致时
if(oldNode.tagName === newNode.tagName&&oldNode.key===newNode.key){
//继续比对属性
let props= diffProps(oldNode.props,newNode.props);
curPatches.push({type:'changeProps',props});
//递归进入下一层级的比较
diffChildrens(oldNode.children,newNode.children,index,patches)
}else{
//当tagName或者key修改后,表示已经是全新节点,无需再比
curPathes.push({type:'replaceNode',node:newNode});
}
}
//构建出整颗差异树

if(curPatches.length){
if(patches[index]){
patches[index] = patches[index].contact(curPatches)
}else{
patches[index] = curPatches;
}
}
}

//属性对比实现
function diffProps(oldProps,newProps){
let propsPatches = [];
// 遍历新旧属性列表
// 查找删除项
// 查找修改项
// 查找新增项
forEach(oldProps,(k,v)=>{
if(!newProps.hasOwnProperty(k)){
propsPatches.push({type:'remove',prop:k});
}else{
if(v!==newProps[k]){
propsPatches.push({type:'change',prop:key,value:newProps[k]});
}
}
})
forEach(newProps,(k,v)=>{
if(!oldProps.hasOwnProperty){
propsPatches.push({type:'add',prop:k,value:v});
}
})
return propsPatches;
}

//对比子级差异
function diffChildrens(oldChild,newChild,index,patches){
//标记子级的删除/新增/移动
let {change,list} = diffList(oldChild,newChild,index,patches);
if(change.length){
if(patches[index]){
patches[index]=patches[index].contact(change);
}else{
patches[index]=change;
}
}

//根据key获取原本匹配的节点,进一步递归从头开始
oldChild.map((item,i)=>{
let keyIndex = list.indexOf(item.key);
if(keyIndex){
let node = newChild(keyIndex);
//进一步递归比对
dfs(item,node,index,patches);
}
})

}

// 列表对比,主要也是根据 key 值查找匹配项
// 对比出新旧列表的新增/删除/移动
function diffList(oldList,newList,index,patches){
let change=[];
let list = [];
const newKeys = getKey(newList);
oldList.map(v=>{
if(newKeys.indexOf(v.key)>=-1){
list.push(v.key);
}else{
list.push(null);
}
})

//标记删除
for(let i = list.length-1;i>=0;i--){
if(!list[i]){
list.splice(i,1);
change.push({type:'remove',index:i});
}
}

//标记新增和移动
newList.map((item,i)=>{
const key = item.key;
const index = list.indexOf(key);
if(index===-1||key==null){
//新增
change.push({type:'add',node:item,index:i});
list.splice(i,0,key);
}else{
//移动
if(index!==i){
change.push({
type:'move',
from:index,
to,i,
})
move(list,index,i);
}
}
})
return {change,list}
}