function cloneFiber(old, new){
return {
...oldFiber,
...newFiber
}
}
function Fiber(old, new){
return {...new }
}
function reconcileArray(
oldArray,
newArray,
){
// diff 结果存放数组
const resultArr = [];
// 最后一个复用的位置
let lastPlacedIndex = 0;
// 遍历新数组对象的索引
let newIdx = 0;
// 遍历旧数组对象的索引
let oldIdx = 0;
// 第一轮遍历
for (; oldIdx < oldArray.length && newIdx < newArray.length; newIdx++, oldIdx++) {
const oldItem = oldArray[oldIdx];
const newItem = newArray[newIdx];
let resultItem = null;
if(oldItem.key === newItem.key) {
if(oldItem.type === newItem.type) {
// key 和 type 都相同 复用
resultItem = cloneFiber(oldItem, newItem);
} else {
// key 相同,type 不相同生成一个新的节点
resultItem = new Fiber(newItem);
// 原来的节点删除
oldItem.flag = 'Deletion';
resultArr.push(oldItem);
}
} else {
// key 不相同跳出第一轮循环
break;
}
// 更新最后一个复用的位置,并标记对应的副作用
lastPlacedIndex = placeChild(oldArray, newItem, lastPlacedIndex);
resultArr.push(newItem)
}
if (newIdx === newArray.length) {
// 说明新节点已经遍历完了,需要给剩下的老节点标记删除
for(; oldIdx < oldArray.length; oldIdx++) {
const oldItem = oldArray[oldIdx]
oldItem.flag = 'Deletion';
resultArr.push(oldItem);
}
// diff结束
return resultArr;
}
if (oldIdx === oldArray.length) {
// 旧节点已经全部遍历完了,新节点还有,对剩下的新节点标记更新
for(; newIdx < newArray.length; newIdx++) {
const newItem = newArray[newIdx]
newItem.flag = 'Placement';
resultArr.push(newItem);
}
// diff 结束
return resultArr;
}
// 将剩下的老节点用key值存储到 map 中方便快速查找
const existingChildren = mapRemainingChildren(oldArray, oldIdx);
// 对剩下的新节点进行遍历,并对老节点尽可能的复用
for (; newIdx < newArray.length; newIdx++) {
const newItem = newArray[newIdx];
// 根据 key 获取对应的老节点
const oldItem = existingChildren.get(newItem.key || newIdx);
let resultItem = null;
if(oldItem) {
// 找到了对应的老节点
if(oldItem.key === newItem.key) {
if(oldItem.type === newItem.type) {
// key 和 type 都相同 复用
resultItem = cloneFiber(oldItem, newItem);
} else {
// key 相同,type 不相同生成一个新的节点
resultItem = new Fiber(newItem);
// 原来的节点删除
oldItem.flag = 'Deletion';
resultArr.push(oldItem);
}
}
// 将 map 中对应的老节点删除
existingChildren.delete(newItem.key || newIdx);
} else{
// 没找到对应的老节点 生成新节点
resultItem = {...newItem};
}
// 更新最后一个复用的位置,并标记对应的副作用
lastPlacedIndex = placeChild(oldArray, resultItem, lastPlacedIndex);
resultArr.push(resultItem);
}
// 将 map 中剩下的节点都标记为删除
existingChildren.forEach(item => {
item.flag = 'Deletion';
resultArr.push(item);
});
return resultArr;
}
function placeChild(oldArray, newItem,lastPlacedIndex) {
// 这里是替代 react 中取老节点的index
const oldIdx = oldArray.findIndex(item => newItem.key === item.key);
if(oldIdx < 0) {
// 没有找到对应的老节点,说明是新增
newItem.flag = 'Placement';
} else {
// 找到了老节点
if(oldIdx < lastPlacedIndex) {
// 说明移动了
newItem.flag = 'Placement-Move';
return lastPlacedIndex
} else {
// 没有移动,同时更新最后复用的位置
return oldIdx
}
}
}
function mapRemainingChildren(oldArr, index) {
const map = new Map();
for(let i = index; i < oldArr.length; i++) {
const item = oldArr[i];
if(item.key) {
// key 存在
map.set(item.key, item);
} else {
// key 不存在 用 index 替代 key 值
map.set(index, item)
}
}
return map;
}
-
cloneFiber函数:这个函数用于克隆旧的 Fiber 节点并与新的 Fiber 节点进行合并。它将旧 Fiber 节点的属性和状态与新 Fiber 节点的属性和状态进行合并。 -
Fiber函数:这个函数用于创建一个新的 Fiber 节点,它将传入的新节点属性对象进行复制。 -
reconcileArray函数:这个函数是整个协调算法的核心。它接收两个数组作为参数:oldArray为旧节点数组,newArray为新节点数组。函数的主要作用是将新旧节点数组进行对比,并生成 diff 结果数组resultArr。- 在第一轮遍历中,它会比较每个节点的
key和type,根据不同情况生成对应的操作:复用、更新或删除。 - 如果新节点数组已遍历完,但旧节点数组还有剩余节点,它会将剩余的旧节点标记为删除。
- 如果旧节点数组已遍历完,但新节点数组还有剩余节点,它会将剩余的新节点标记为新增。
- 在第一轮遍历中,它会比较每个节点的
-
placeChild函数:这个函数根据key值在旧节点数组中查找对应的节点,并判断该节点是否发生了移动。它返回最后一个复用的位置,以便在插入新节点时进行参考。 -
mapRemainingChildren函数:这个函数用于将剩余的旧节点映射到一个 Map 数据结构中,以便在后续遍历中快速查找并标记删除。 -
在主程序中,
old和newArr分别表示旧节点数组和新节点数组。通过调用reconcileArray函数对比两个数组,得到 diff 结果result,然后输出结果。