为了尽量不宜动,先左右夹击跳过不变的,再找到最长连续子串保持不动,移动其他元素
采用了仅右移动方案,在大部分从左往右移的业务场景中,得到了较好的性能
参考文章 深入理解 react》之 DIFF 算法 说明
_.old.js 和 _.new.js 共存的原因: React 团队在实现 Suspense 和 Concurrent。这两个依赖的时间过期模型遍布 reconciler,所以很难通过几个 tag 来标记改动,并且不影响正常迭代。
对于单个节点,没有 diff 的意义,直接判断就好了,因此 diff 只会发生在某一层有多个节点的情况,核心就是 reconcileChildrenArray 这个函数
function reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChildren,
lanes
) {
var resultingFirstChild = null; // 新构建的第一个childFiber
var previousNewFiber = null; // 前一个新fiber
var oldFiber = currentFirstChild; // 第一个oldFiber
var lastPlacedIndex = 0; // 标记
var newIdx = 0; // 索引
var nextOldFiber = null;
// 第一轮判断
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
var newFiber = updateSlot( // 从第一个old fiber 开始与 第一个 newChild 比较,看是否相同
returnFiber,
oldFiber,
newChildren[newIdx],
lanes
);
if (newFiber === null) { // 说明开始不同了 开始退出;
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
// 说明 新的ReactElements被用完了 , 直接删除所有的旧节点就好了
if (newIdx === newChildren.length) {
deleteRemainingChildren(returnFiber, oldFiber); // 标记删除节点
return resultingFirstChild;
}
// 说明 oldFiber 被用完了,直接创建新的节点就好了
if (oldFiber === null) {
for (; newIdx < newChildren.length; newIdx++) {
var _newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (_newFiber === null) {
continue;
}
lastPlacedIndex = placeChild(_newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = _newFiber;
} else {
previousNewFiber.sibling = _newFiber;
}
previousNewFiber = _newFiber;
}
return resultingFirstChild;
}
// 说明都没有被用完,只是找到不同的节点而已,开始diff真正的逻辑;
var existingChildren = mapRemainingChildren(returnFiber, oldFiber); // Keep scanning and use the map to restore deleted items as moves.
for (; newIdx < newChildren.length; newIdx++) {
var _newFiber2 = updateFromMap( // 从existingChildren中找存在的fiber
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
lanes
);
if (_newFiber2 !== null) {
if (_newFiber2.alternate !== null) {
existingChildren.delete(
_newFiber2.key === null ? newIdx : _newFiber2.key
);
}
lastPlacedIndex = placeChild(_newFiber2, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = _newFiber2;
} else {
previousNewFiber.sibling = _newFiber2;
}
previousNewFiber = _newFiber2;
}
}
existingChildren.forEach(function (child) {
return deleteChild(returnFiber, child);
});
return resultingFirstChild;
}
第一轮判断,找出不同
真正的 diff 逻辑