DIFF核心函数 -- reconcileChildFibers
目的:生成新fiber diff源于脏检查
diff对象: workInProgress 的 oldChildrenFiber 与 newChildren (jsx) 进行对比。
优化: Fragment 若无key,则直接取 children 进行创建,不创建 FragmentFiber
无法复用:1. key不同 2.ket同type不同
newChildren不同情况,进行不同diff。这里只分析3种情况
单JSX (newChildren值为单个jsx) -- reconcileSingleElement
- **循环 **
oldChildrenFiber。childFiber与newChild(jsx)进行对比key相同- 若
newChild是Fragment且oldFiber.tag也是Fragment- 标记删除剩余
oldChildrenFiber--deleteChild() existing= 获取**复用fiber **--useFiber()- 更新index,ref,return等属性。(会复用dom)
- return
existing
- 标记删除剩余
- 若
type相同- 标记删除剩余
oldChildrenFiber--deleteChild() existing= 获取复用fiber --useFiber()- 更新index,ref,return等属性。(会复用dom)
- return
existing
- 标记删除剩余
- 走到此,说明
key对应的组件无法复用 - 标记删除剩余
oldChildrenFiber--deleteChild()
- 若
key不同- 标记删除
childFiber--deleteChild()
- 标记删除
child = child.sibling
- 走到这,说明无可复用fiber。
- created = 创建
新fiber--createFiberFromElement()Fragment则createFiberFromFragment()
- return created
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
lanes: Lanes,
): Fiber {
const key = element.key;
let child = currentFirstChild;
while (child !== null) { // fiber update
if (child.key === key) { // key相同
const elementType = element.type;
// 复用Fragment
if (elementType === REACT_FRAGMENT_TYPE) {
if (child.tag === Fragment) {
// 删除
deleteRemainingChildren(returnFiber, child.sibling);
// 复用fiber
const existing = useFiber(child, element.props.children);
existing.return = returnFiber;
return existing;
}
} else {
// 复用其余type相同组件,以及复用lazy
if (
child.elementType === elementType ||
(enableLazyElements &&
typeof elementType === 'object' &&
elementType !== null &&
elementType.$$typeof === REACT_LAZY_TYPE &&
resolveLazy(elementType) === child.type)
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.ref = coerceRef(returnFiber, child, element);
existing.return = returnFiber;
return existing;
}
}
// 删除所有,因为key匹配的无法复用,剩余都是key不匹配的也没法用
deleteRemainingChildren(returnFiber, child);
break;
} else {
//删除
deleteChild(returnFiber, child);
}
child = child.sibling;
}
// 创建 Fragment fiber
if (element.type === REACT_FRAGMENT_TYPE) {
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
lanes,
element.key,
);
created.return = returnFiber;
return created;
} else {
// 创建 ReactElement fiber
const created = createFiberFromElement(element, returnFiber.mode, lanes);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
created.return = returnFiber;
return created;
}
}
文本 (newChildren值为string或number时) -- reconcileSingleTextNode
注意:react对于原生组件 chilren 是单文本节点的情况进行优化,不会对其生成fiber
- 不生成fibet => updateHostComponent中将newChildren设置为null
- 节点插入文本 => completeWork-> case HostComponent -> finalizeInitialChildren -> setInitialProperties -> setInitialDOMProperties -> typeof nextProp === 'string'或'number' -> setTextContent
实现:
- 若
firstChildFiber是否为文本节点- 标记删除其余
siblingFiber--deleteChild()existing= 获取复用fiber --useFiber()- 更新index,return等属性。(会复用dom)
- return
existing
- 标记删除其余
- 走到这里,说明无法复用。
- 标记删除
childrenFiber--deleteChild() - created = 创建
新fiber--createFiberFromText() - return created
function reconcileSingleTextNode(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
textContent: string,
lanes: Lanes,
): Fiber {
// 复用
if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
const existing = useFiber(currentFirstChild, textContent);
existing.return = returnFiber;
return existing;
}
// 删除 新建
deleteRemainingChildren(returnFiber, currentFirstChild);
const created = createFiberFromText(textContent, returnFiber.mode, lanes);
created.return = returnFiber;
return created;
}
数组 (newChildren值为array,数组项为对应jsx) -- reconcileChildrenArray
- 注意:
- O(n)复杂度;
oldChildrenFiber和newChildrenJSX都只遍历一次,遍历就会被处理。 - 节点变化处理优先级:更新 > 删除 > 新增 > 位置变化
- O(n)复杂度;
- 模块变量:
resultingFirstChild: 最终返回的fiber。previousNewFiber: 最新创建的newFiber。lastPlacedIndex: 最后一个安置(Placement)的fiber。oldFiber:表示正在遍历的fiber, 从currentFirstChild遍历sibling到null。newChild:表示正在遍历的jsx。从newChildrenJSX和newIdx确定。newIdx: 表示正在遍历的newChild的索引。
-
- 处理更新
- 什么是更新?key不变,即算更新。 更新时尽量复用fiber,dom
- 不写key,默认为null,新旧key都为null,也是key不变。
- 循环
oldChildrenFiber** 和newChildrenJSX**。childFiber与newChild(jsx)进行对比- 生成
newFiber--updateSlot() - 若
newFiber是null- 说明此次不是更新。即key不同。
- break
- 若
newFiber**没有复用 **oldFiber,标记删除oldFiber。--deleteChild()- 判断
oldFiber && newFiber.alternate === null
- 判断
- 安置
newFiber- lastPlacedIndex =
placeChild(newFiber, lastPlacedIndex, newIdx);
- lastPlacedIndex =
- 判断
newFiber是否为第一个子节点- 是
resultingFirstChild赋值为newFiber。
- 否
newFiber通过sibling连接前一个子节点。reviousNewFiber.sibling = newFiber
- 是
previousNewFiber = newFiberoldFiber = nextOldFiber
- 生成
-
- 处理删除
- 进入条件
newIdx === newChildren.length** **。newChildren已全被遍历,未被遍历的oldChildrenFiber不再需要,全部删除。
- 标记删除剩余
oldChildrenFiber--deleteChild() - return
resultingFirstChild
-
- 处理**新增 **
- 进入条件
oldFiber === null** **。oldChildrenFiber已全被遍历,未被遍历的newChildren全部为新创建的fiber。
- **循环
newChildrenJSX**。- 生成
newFiber。 - 放置
newFiber。--placeFiber() - 判断
newFiber是否为第一个子节点- 是
resultingFirstChild赋值为newFiber。
- 否
newFiber通过sibling连接前一个子节点。reviousNewFiber.sibling = newFiber
- 是
previousNewFiber = newFiber
- 生成
- return
resultingFirstChild
-
- 处理位置变化
- 进入条件
oldChildrenFiber和newChildren都没遍历完。 剩余oldChildrenfiber生成Map(key/index, childFiber)--mapRemainingChildren()- Map的key优先取值fiber.key,若为null,取fiber.index。value值为fiber。
- **循环
newChildrenJSX**。- 生成
newFiber--updateFromMap()- 通过
key/index在Map中取oldFiber。 - 有则复用,无则新建。
- 通过
- 若为
复用fiber,删除Map对应索引。 - 安置
newFiber- lastPlacedIndex =
placeChild(newFiber, lastPlacedIndex, newIdx)
- lastPlacedIndex =
- 判断
newFiber是否为第一个子节点- 是
resultingFirstChild赋值为newFiber。
- 否
newFiber通过sibling连接前一个子节点。reviousNewFiber.sibling = newFiber
- 是
previousNewFiber = newFiber
- 生成
-
- 删除Map中
未复用用fiber。--deleteChild()
- 删除Map中
-
- return
resultingFirstChild。
- return
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;
// 循环功能: 只处理更新
// 循环终止条件: 1. oldChildren / newChildren 遍历完了
// 2. oldFiber与newElement(jsx) key不同(位置交换), 在当前项 break
// 更新是什么意思? key不变,即算更新. 节点类型,内容,props
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
// index是在jsx中的位置,位置变了 跳出
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else {
nextOldFiber = oldFiber.sibling;
}
// 尝试update fiber
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
lanes,
);
// 能否更新?
if (newFiber === null) {
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
break;
}
if (shouldTrackSideEffects) {
if (oldFiber && newFiber.alternate === null) {
// 不复用 删除oldFiber
deleteChild(returnFiber, oldFiber);
}
}
// 将需要插入的fiber flags写入Placment
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// fiberChildren头
resultingFirstChild = newFiber;
} else {
// 构建tree
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
if (newIdx === newChildren.length) {
// 新的完成,删除剩余旧fiber
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
if (oldFiber === null) {
// oldFiber遍历结完
// 新的全部生成fiber
for (; newIdx < newChildren.length; newIdx++) {
const 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;
}
// 生成Map(key/index,fiber)
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// 通过Map来复用
for (; newIdx < newChildren.length; newIdx++) {
// 复用 / 新生成
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
lanes,
);
if (newFiber !== null) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// Map去除复用的fiber
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key,
);
}
}
// 将需要插入的fiber flags写入Placment
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects) {
// Map 复用结束,删除剩余无法复用的fiber
existingChildren.forEach(child => deleteChild(returnFiber, child));
}
return resultingFirstChild;
}
相关函数
deleteChild -- 标记删除childFiber
deleteChild(returnFiber: Fiber, childToDelete: Fiber)
- 功能:
- 收集要删除的
childFiber。commit阶段进行删除。
- 收集要删除的
- 逻辑
returnFiber.flags标记ChildDeletionreturnFiber.deletions数组pushchildFiber
function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
const deletions = returnFiber.deletions;
if (deletions === null) {
returnFiber.deletions = [childToDelete];
returnFiber.flags |= ChildDeletion;
} else {
deletions.push(childToDelete);
}
}
useFiber -- 复用fiber
useFiber(fiber: Fiber, pendingProps: mixed)
- 功能:
- 复用fiber。
- 逻辑
- clone = 生成新workInProgress,复用/新建fiber。
- 复用:fiber有alternate
- 新建:fiber没alternate
- 重置props,index,sibling
- clone.pendingProps = pendingProps
- clone.index = 0;
- clone.sibling = null
- return clone
- clone = 生成新workInProgress,复用/新建fiber。
function useFiber(fiber: Fiber, pendingProps: mixed): Fiber {
const clone = createWorkInProgress(fiber, pendingProps);
clone.index = 0;
clone.sibling = null;
return clone;
}
placeChild -- 安置fiber
placeChild(newFiber: Fiber, lastPlacedIndex: number, newIndex: number )
- 功能:
- 判断
newFiber是否需要安置Palcement。
- 判断
- 变量
lastPlacedIndex:最后一个**复用且不 ****Placement**的fiber.index。- 最后一个已经插入到
dom且不用变化位置的fiber.index,即复用fiber。
- 最后一个已经插入到
- 思路:
newFiber分为复用fiber,和新建fiber。- React是如何插入DOM?
- insertOrAppendPlacementNode
- 若
fiber右侧sibling中有复用fiber,则insertBefore,插入到它左侧。 - 若
fiber右侧sibling中无复用fiber,则appenChild,依次插入。
- 若
复用fiber在最后一个复用fiber的左侧,则移动到右侧(安置)。 - 若
复用fiber不在最后一个复用fiber的左侧,则修改lastPlacedIndex为oldIndex。- 因
fiber.index值为其在childrenJSX数组的index,且lastPlacedIndex只会赋值oldFiber.index。 - 所以
lastPlacedIndex的值必定为childrenJSX数组范围内的值。lastPlacedIndex初始值为0。因为数组起点是0。
- 因
- 若为
新建fiber直接安置。
- 逻辑
- 若为mount,直接return lastPlacedIndex
- newFiber.index = newIndex;
newFiber的index为jsx在newChildrenJSX的索引。 - 判断是否为复用
fiber- 否
- 标记安置(Placement)
- return lastPlacedIndex
- 是·
- 判断
复用fiber是否在最后一个复用fiber的左侧。oldFiber.index < lastPalcedIndex。
- 在左侧 <
- 标记安置(Placement)
- return lastPlacedIndex
- 不在左侧 >=, 修改lastPlacedIndex
- return oldIndex。 会赋值给lastPlacedIndex
- 判断
- 否
function placeChild(
newFiber: Fiber,
lastPlacedIndex: number,
newIndex: number,
): number {
newFiber.index = newIndex;
const current = newFiber.alternate;
if (current !== null) {
const oldIndex = current.index;
// 若复用fiber 在 最后一个复用fiber 的 左侧 重新Placment
if (oldIndex < lastPlacedIndex) {
newFiber.flags |= Placement;
return lastPlacedIndex;
} else {
// 若复用fiber 不在左侧, 修改lastPlacedIndex
return oldIndex;
}
} else {
// 新fiber直接Placment
newFiber.flags |= Placement;
return lastPlacedIndex;
}
}