多个节点数量和key相同,type也相同
全部复用老节点,只更新属性有就可以了
多个节点数量和key相同,有的type不同
多个节点类型和key完全 相同,有新增的元素
因为新的比老的数量多,第一轮循环结束的时候,老的已经遍历完成,新的还有剩下的
插入剩下的
多个节点类型和key完全 相同,老节点有多余的元素
删除多余的老节点
至少有一边遍历完成
多个节点数量不同 ,key也不同
<li key="A" id='a'>A</li>
<li key="B" id='b'>B</li>
<li key="C" id='c'>C</li>
<li key="D" id='d'>D</li>
<li key="E" id='e'>E</li>
<li key="F" id='f'>F</li>
<li key="A" id='a2'>A2</li>
<li key="C" id='c2'>C2</li>
<li key="E" id='e2'>E2</li>
<li key="B" id='b'>B</li>
<li key="G" id='g'>G</li>
<li key="D" id='d2'>D2</li>
开启三轮循环
1.第一轮循环
如果key不同则立刻跳出第一轮循环
第对比A,key和类型一样,则复用A
再对比B和C,key不一样,则立刻跳出第一轮循环
2.第2轮循环
只有老的或新的遍历完成才会走第2轮循环
3.开启第3循环
1.从B开始,把所有的老节点全闻放到一个 map中
实现原理
//1.创建一个map,存放剩下老节点
const existingChildren = new Map();
let nodeA = {key:'A',mountIndex:0};
let nodeB = {key:'B',mountIndex:1};
let nodeC = {key:'C',mountIndex:2};
let nodeD = {key:'D',mountIndex:3};
let nodeE = {key:'E',mountIndex:4};
let nodeF = {key:'F',mountIndex:5};
//剩下的,等待被复用的老节点
existingChildren.set('B',nodeB);
existingChildren.set('C',nodeC);
existingChildren.set('D',nodeD);
existingChildren.set('E',nodeE);
existingChildren.set('F',nodeF);
//2.声明一个变量 上一个被放置好的不需要移动的老节点的的索引
let lastPlacedIndex = -1;
//3.开始遍历剩下的新节点
let newNodes = [
{key:'C',mountIndex:1},
{key:'E',mountIndex:2},
{key:'B',mountIndex:3},
{key:'G',mountIndex:4},
{key:'D',mountIndex:5}
]
let patch = [];
//开始按顺序遍历剩下的新节点
for(let i=0;i<newNodes.length;i++){
//得到当前循环的新节点
let newNode = newNodes[i];
//通过key去老节点的map中查找可以复用的,key相同的老节点
let oldNode = existingChildren.get(newNode.key);
//如果找到key相同的可以复用的老节点,就可以复用此老节点
if(oldNode){
//如果复用了,就可以把此老节点从existingChildren中删除
existingChildren.delete(newNode.key);
//让lastPlacedIndex等于复用的老节点的挂载索引
lastPlacedIndex=newNode.mountIndex;//lastPlacedIndex=2
//如果可复用的老节点的索引小于上一个不需要移动的老节点索引的话,
//那就需要移动老节点,此时不需要更新lastPlacedIndex
if(oldNode.mountIndex<lastPlacedIndex){
patch.push(`复用、更新并移动${newNode.key},从${oldNode.mountIndex}到${newNode.mountIndex}`);
}else{
patch.push(`复用并更新${newNode.key}`);
//如果找到可复用的老节点挂载索引比当前lastPlacedIndex要大,则它不需要移动,
//但需要更新lastPlacedIndex为oldNode.mountIndex
lastPlacedIndex=oldNode.mountIndex;
}
}else{
//如果在map中找不到能复用的老节点,则创建新节点
patch.push(`创建新的${newNode.key}`);
}
}
for(let key of existingChildren.keys()){
patch.push(`删除 ${key}`);
}
console.log('patch',patch)
//最后会把留在existingChildren中的元素全部删除
手动实现react中虚拟DoM到真实DOM
const ReactDOM = {
//创一个渲染DOM的容器
createRoot(container) {
return {
//把虚拟DOM也就是react元素转成真实DOM并且插入container容器中
render(reactElement) {
//如果有就意味着会走更新逻辑,也会走DOM-DIFF
if(this.oldReactElement){
return update(this.oldReactElement,reactElement);
}
//把虚拟DOM或者说React元素转成真实DOM
const domElement = this.createDOMElement(reactElement);
//把转化成的真实DOM插入到DOM容器中
container.appendChild(domElement);
//把本次渲染的虚拟DOm保存到this.oldReactElement
this.oldReactElement = reactElement;
},
update(oldReactElement,newReactElement){
//比较oldReactElement和newReactElement
//比较完成会会得到DOM操作补丁包
let patch = [
'把div的样式对象中的属性style变成color:red变成color:green',
'从div中删除span子节点',
'向div中添加p子节点'
]
// 应用DOM操作补丁包到真实DOM
},
//根据虚拟DOM创建真实DOM
createDOMElement(reactElement) {
//如果React元素是一个字符串,那么会创建并返回一个文本节点
if(typeof reactElement === 'string')
return document.createTextNode(reactElement);
//获取React元素的类型和属性对象
const { type, props } = reactElement;
//创建此类型对应的真实DOM元素
const domElement = document.createElement(type);
//如果有属性的话
if(props){
//遍历属性对象的属性的数组 keys for(let key in props)
//div props ['style','children'] span props = ['id','children']
Object.keys(props).forEach(key=>{
//如果key是style说明是要给DOM元素的行内样式赋值
if(key === 'style'){
//获取样式对象
let styleObj = props[key];//{ "color": "red"}
//遍历样式对象的属性的数组 ['color']
Object.keys(styleObj).forEach((styleKey)=>{//styleKey=color
//给真实DOM的样式对象赋值
domElement.style[styleKey]=styleObj[styleKey];
//div.style.color = red;
});
}else if(key === 'children'){//如果属性是children的话
//递归遍历儿子,把每个儿子都变成一个真实DOM元素并且挂载到自己身上
//为了方便处理,把children变成数组
const children = Array.isArray(props.children)?props.children:[props.children]
//遍历所有的儿子,把每个儿子变成真实DOM并且挂载到自己身上
children.forEach(childElement=>{
//递归调用createDOMElement方法,把虚拟DOM变成真实DOM childDOMElement
const childDOMElement = this.createDOMElement(childElement);
//把子元素对应的真实DOM添加到自己身上
domElement.appendChild(childDOMElement);
});
}else{//id className 普通属性 就可以直接给DOM赋值就可以
domElement[key]=props[key];
//span.id = 'title';
}
});
}
return domElement;
}
}
}
}
/**
{
"type": "div",
"props": {
"style": {
"color": "red"
},
"children": [
"hello",
{
"type": "span",
"props": {
"id": "title",
"children": "world"
}
}
]
}
}
*/
export default ReactDOM;