共计 6788 个字符,预计需要花费 17 分钟才能阅读完成。
两种节点类型
咱们能够从同级的节点数量将 Diff 分为两类:
当 newChild 类型为 object、number、string,代表同级只有一个节点
当 newChild 类型为 Array,同级有多个节点
在接下来两节咱们会别离探讨这两类节点的 Diff,留神这里的单节点是指虚构 dom 节点是个单或者多节点,能够简略看做是不是返回的数组
单节点
单节点比拟还是比较简单的
// 删除节点
function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {if (!shouldTrackSideEffects) {
// Noop.
return;
}
//effect 链的解决
const last = returnFiber.lastEffect;
if (last !== null) {
last.nextEffect = childToDelete;
returnFiber.lastEffect = childToDelete;
} else {
// 证实临时还没有造成链须要第一个节点
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
}
const deletions = returnFiber.deletions;
if (deletions === null) {returnFiber.deletions = [childToDelete];
// TODO (effects) Rename this to better reflect its new usage (e.g. ChildDeletions)
returnFiber.effectTag |= Deletion;
} else {deletions.push(childToDelete);
}
childToDelete.nextEffect = null;
}
// 批量删除节点的工具函数(更精确的是批量标记)function deleteRemainingChildren(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
): null {if (!shouldTrackSideEffects) {
// Noop.
return null;
}
// TODO: For the shouldClone case, this could be micro-optimized a bit by
// assuming that after the first child we've already added everything.
let childToDelete = currentFirstChild;
while (childToDelete !== null) {deleteChild(returnFiber, childToDelete);
childToDelete = childToDelete.sibling;
}
return null;
}
//element 其实就是新的虚构 dom
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement
): Fiber {
const key = element.key;
let child = currentFirstChild;
// 首先判断是否存在对应 DOM 节点
while (child !== null) {
// 上一次更新存在 DOM 节点,接下来判断是否可复用
// 首先比拟 key 是否雷同
if (child.key === key) {
// key 雷同,接下来比拟 type 是否雷同
switch (child.tag) {
// ... 省略 case
default: {if (child.elementType === element.type) {
// type 雷同则示意能够复用
deleteRemainingChildren(returnFiber, child.sibling);// 显然这个节点的后续节点都必须删除了 因为找到了
const existing = useFiber(child, element.props);//useFiber 故名思义 这里的 element.props 就是后续看是否要调整的属性
// 返回复用的 fiber
return existing;
}
// type 不同则跳出 switch
break;
}
}
// 代码执行到这里代表:key 雷同然而 type 不同
// 将该 fiber 及其兄弟 fiber 标记为删除
deleteRemainingChildren(returnFiber, child);
break;
} else {
// key 不同,将该 fiber 标记为删除
deleteChild(returnFiber, child);
}
child = child.sibling;
}
// 创立新 Fiber,并返回 ... 省略
}
能够发现须要被删除的 fiber 不会在这间接真的删除,而是造成一个 effect 链,另外父节点会保护一个 deletions 的 fiber 数组
首先判断 child 是否存在,不存在则间接开始兄弟节点的比拟,while 终止在同层比拟实现后
几种逻辑分支
- key 雷同,类型也雷同间接可复用,后续就看属性状况更新属性即可
- key 雷同,类型不同了,间接 deleteRemainingChildren 删除这个节点及他的兄弟节点,这里是因为 key 雷同了,后续没有持续比拟找可复用节点的意义了,故把原节点删完就能够了
- key 不同,间接把这个比拟的节点删除
多节点
先整顿一下源码
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
lanes: Lanes,
): Fiber | null {
let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {nextOldFiber = oldFiber.sibling;}
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
lanes,
);
if (newFiber === null) {if (oldFiber === null) {oldFiber = nextOldFiber;}
break;
}
if (shouldTrackSideEffects) {if (oldFiber && newFiber.alternate === null) {
// We matched the slot, but we didn't reuse the existing fiber, so we
// need to delete the existing child.
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// 构建新的 fiber 链作为返回值
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {previousNewFiber.sibling = newFiber;}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
if (newIdx === newChildren.length) {
// We've reached the end of the new children. We can delete the rest.
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
if (oldFiber === null) {
// If we don't have any more existing children we can choose a fast path
// since the rest will all be insertions.
for (; newIdx < newChildren.length; newIdx++) {const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (newFiber === null) {continue;}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {// 构建新的 fiber 链作为返回值
// TODO: Move out of the loop. This only happens for the first run.
resultingFirstChild = newFiber;
} else {previousNewFiber.sibling = newFiber;}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
// Add all children to a key map for quick lookups.
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// Keep scanning and use the map to restore deleted items as moves.
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
lanes,
);
if (newFiber !== null) {if (shouldTrackSideEffects) {if (newFiber.alternate !== null) {
existingChildren.delete(newFiber.key === null ? newIdx : newFiber.key,);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {resultingFirstChild = newFiber;} else {previousNewFiber.sibling = newFiber;}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects) {
// Any existing children that weren't consumed above were deleted. We need
// to add them to the deletion list.
existingChildren.forEach(child => deleteChild(returnFiber, child));
}
return resultingFirstChild;
}
先对几个用到的重要函数解读一下
updateSlot 这个函数能够简略了解为节点比拟,如果不匹配返回 null,不然就是一个可复用的 fiber 节点
function mapRemainingChildren(
returnFiber: Fiber,
currentFirstChild: Fiber,
): Map<string | number, Fiber> {const existingChildren: Map<string | number, Fiber> = new Map();
let existingChild = currentFirstChild;
while (existingChild !== null) {if (existingChild.key !== null) {existingChildren.set(existingChild.key, existingChild);
} else {existingChildren.set(existingChild.index, existingChild);
}
existingChild = existingChild.sibling;
}
return existingChildren;
}
mapRemainingChildren 返回一个 children 形成的 Map key 为 id 或者是 child 的 index
内容比拟多倡议分成三个步骤去看
-
第一个循环,比拟 newChildren 和 oldFiber 和他的兄弟们 会有三种状况
- key 不同循环间接进行
- key 雷同,类型不同,fiber 标记为删除循环持续 i++ oldFiber = nextOldFiber;
- 循环完结 newChildren 或者是 oldFiber 和他的兄弟们遍历完结了
- 循环完解决一下几种状况,一种是 newChildren 现遍历完了,那删除残余的 oldFiber,deleteRemainingChildren(returnFiber, oldFiber); 第二种是 oldFiber 遍历完了,那残余的 newChildren 须要创立 fiber 节点 并且拼接在 previousNewFiber 这个后果链上 触发这两种状况都会退出整个 diff
- 也就是都没有遍历完,状况就是因为节点地位挪动导致的,这个时候先要 mapRemainingChildren(returnFiber, oldFiber); 把残余的 fiber 做一个 Map 映射,而后 newChildren 残余的节点去 Map 中查找,重点是 placeChild 函数
function placeChild(
newFiber: Fiber,
lastPlacedIndex: number,
newIndex: number,
): number {
newFiber.index = newIndex;
if (!shouldTrackSideEffects) {
// Noop.
return lastPlacedIndex;
}
const current = newFiber.alternate;
if (current !== null) {
const oldIndex = current.index;
if (oldIndex < lastPlacedIndex) {
// This is a move.
// 本来节点的 index 比以后最近一次替换过的节点的 index 还小的话标记为挪动,且 lastPlacedIndex 不变
newFiber.effectTag = Placement;
return lastPlacedIndex;
} else {
// This item can stay in place.
// 返回原有节点的地位作为新的 lastPlacedIndex
return oldIndex;
}
} else {
// This is an insertion.
//newChildren 在本来没有 齐全是新建的
newFiber.effectTag = Placement;
return lastPlacedIndex;
}
}
举个例子如果 01234 要变为 12304 假如 lastPlacedIndex 为 0 初始开始循环
1 oldIndex 为 1 oldIndex > lastPlacedIndex 不动 lastPlacedIndex = oldIndex 也就是 1
2 oldIndex 为 2 oldIndex > lastPlacedIndex 不动 lastPlacedIndex = oldIndex 也就是 2
3 oldIndex 为 3 oldIndex > lastPlacedIndex 不动 lastPlacedIndex = oldIndex 也就是 3
0 oldIndex 为 0 oldIndex < lastPlacedIndex 标记挪动 lastPlacedIndex 不变还是 3
4 oldIndex 为 4 oldIndex > lastPlacedIndex 不动 lastPlacedIndex 改为 4