- 给新增,移动,和删除节点设置fiber.falgs(新增, 移动: Placement, 删除: Deletion)
- 如果是需要删除的fiber, 除了自身打上Deletion之外, 还要将其添加到父节点的effects链表中(正常副作用队列的处理是在completeWork函数, 但是该节点(被删除)会脱离fiber树, 不会再进入completeWork阶段, 所以在beginWork阶段提前加入副作用队列).
时间复杂度:
- 从上至下比较整个树形结构, 时间复杂度被缩短到 O(n)
基本比较:
- fiber对象与ReactElement对象相比较,并不是两棵 fiber 树相比较, 而是旧fiber对象与新ReactElement对象向比较, 结果生成新的fiber子节点
比较方案
- 单节点比较、多节点数组类型比较
reconcileChildFibers
function ChildReconciler(shouldTrackSideEffects) {
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
// Handle object types
// 判断是单节点比较还是多节点比较
const isObject = typeof newChild === 'object' && newChild !== null;
if (isObject) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
// newChild是单节点
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
}
}
// newChild是多节点
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
}
// ...
}
return reconcileChildFibers;
}
单节点比较
reconcileSingleElement
- 未匹配到key和type,新建节点,并给当前节点打上Deletion标记
- 匹配到key和type,会复用dom节点,即新fiber对象.stateNode = currentFirstChild.stateNode
// 只保留主杆逻辑
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
lanes: Lanes,
): Fiber {
const key = element.key;
let child = currentFirstChild;
while (child !== null) {
// currentFirstChild !== null, 表明是对比更新阶段
if (child.key === key) {
// 1. key相同, 进一步判断 child.elementType === element.type
switch (child.tag) {
// 只看核心逻辑
default: {
if (child.elementType === element.type) {
// 1.1 已经匹配上了, 如果有兄弟节点, 需要给兄弟节点打上Deletion标记
deleteRemainingChildren(returnFiber, child.sibling);
// 1.2 构造fiber节点, 新的fiber对象会复用current.stateNode, 即可复用DOM对象
const existing = useFiber(child, element.props);
existing.ref = coerceRef(returnFiber, child, element);
existing.return = returnFiber;
return existing;
}
break;
}
}
// Didn't match. 给当前节点打上Deletion标记
deleteRemainingChildren(returnFiber, child);
break;
} else {
// 2. key不相同, 匹配失败, 给当前节点打上Deletion标记
deleteChild(returnFiber, child);
}
child = child.sibling;
}
//...
// 新建节点,未匹配上
const created = createFiberFromElement(element, returnFiber.mode, lanes);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
created.return = returnFiber;
return created;
}
createChild
function createChild(
returnFiber: Fiber,
newChild: any,
lanes: Lanes,
): Fiber | null {
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
// 调用构造函数进行创建
const created = createFiberFromElement(
newChild,
returnFiber.mode,
lanes,
);
return created;
}
}
}
return null;
}
多节点比较
- reconcileChildrenArray实际就是 2 个序列之间的比较(链表oldFiber和数组newChildren), 最后返回尽量复用之前的fiber的序列.
- 遍历最长公共序列(key 相同), 公共序列的节点都视为可复用
- 如果newChildren序列被遍历完, 那么oldFiber序列中剩余节点都视为删除(打上Deletion标记)
- 如果oldFiber序列被遍历完, 那么newChildren序列中剩余节点都视为新增(打上Placement标记)
- 遍历剩余非公共序列, 优先复用 oldFiber 序列中的节点
第二次循环, 会遍历剩余序列E,C,X,Y
- 生成的 fiber 节点fiber(E), fiber©可以复用. 其中fiber©节点发生了位移(打上Placement标记).
- fiber(X), fiber(Y)是新增(打上Placement标记).
- 同时oldFiber序列中的fiber(D)节点确定被删除(打上Deletion标记).
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
lanes: Lanes,
): Fiber | null {
if (__DEV__) {
// First, validate keys.
let knownKeys = null;
for (let i = 0; i < newChildren.length; i++) {
const child = newChildren[i];
// 1. 在dev环境下, 执行warnOnInvalidKey.
// - 如果没有设置key, 会警告提示, 希望能显示设置key
// - 如果key重复, 会错误提示.
knownKeys = warnOnInvalidKey(child, knownKeys, returnFiber);
}
}
//比较完成最终的fiber链表结构
let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild;
//需要移动的fiber位置
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
// 1. 第一次循环: 遍历最长公共序列(key相同), 公共序列的节点都视为可复用
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
//移动指针,获取下一个oldFiber
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
// new槽位和old槽位进行比较, 如果key不同, 返回null
// key相同, 比较type是否一致. type一致则执行useFiber(update逻辑), type不一致则运行createXXX(insert逻辑)
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
lanes,
);
if (newFiber === null) {
// 如果返回null, 表明key不同. 无法满足公共序列条件, 退出循环
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
// 若是新增节点, 则给老节点打上Deletion标记
if (oldFiber && newFiber.alternate === null) {
deleteChild(returnFiber, oldFiber);
}
}
// lastPlacedIndex 记录被移动的节点索引
// 如果当前节点可复用, 则要判断位置是否移动.
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
// 更新resultingFirstChild结果序列
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
//移动旧fiber指针
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
//第一次公共节点遍历完毕
if (newIdx === newChildren.length) {
// 如果newChildren序列被遍历完, 那么oldFiber序列中剩余节点都视为删除(打上Deletion标记)
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
//第一次公共节点遍历完毕
if (oldFiber === null) {
// 如果oldFiber序列被遍历完, 那么newChildren序列中剩余节点都视为新增(打上Placement标记)
for (; newIdx < newChildren.length; newIdx++) {
//
}
return resultingFirstChild;
}
// ==================分割线==================
//将第一次循环后, oldFiber剩余序列加入到一个map中. 目的是为了第二次循环能顺利的找到可复用节点
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// 2. 第二次循环: 遍历剩余非公共序列, 优先复用oldFiber序列中的节点
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
lanes,
);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// 如果newFiber是通过复用创建的, 则清理map中对应的老节点
existingChildren.delete(newFiber.key === null ? newIdx : newFiber.key);
}
}
// 更新移动fiber索引
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
// 更新resultingFirstChild结果序列
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects) {
// newChildren已经遍历完, 那么oldFiber序列中剩余节点都视为删除(打上Deletion标记)
existingChildren.forEach(child => deleteChild(returnFiber, child));
}
return resultingFirstChild;
}
updateSlot
function updateSlot(
returnFiber: Fiber,
oldFiber: Fiber | null,
newChild: any,
lanes: Lanes,
): Fiber | null {
const key = oldFiber !== null ? oldFiber.key : null;
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
//key用于是否复用的第一判断条件
if (newChild.key === key) {
return updateElement(returnFiber, oldFiber, newChild, lanes);
} else {
return null;
}
}
}
}
return null;
}
updateFromMap
function updateFromMap(
existingChildren: Map<string | number, Fiber>,
returnFiber: Fiber,
newIdx: number,
newChild: any,
lanes: Lanes,
): Fiber | null {
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
//key用于是否复用的第一判断条件
//查找复用旧fiber节点
const matchedFiber =
existingChildren.get(
//如果没有key就会用索引来当作key
newChild.key === null ? newIdx : newChild.key,
) || null;
return updateElement(returnFiber, matchedFiber, newChild, lanes);
}
}
return null;
}