「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战」
上一篇文章提到了 reconcileSingleElement 调用,今天来看看这个代码做了啥。
源码分析
函数签名如下:
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
lanes: Lanes,
): Fiber {}
事实上,currentFirstChild 是 returnFiber.alternate.child。我们以前提过,Fiber 除了有 child 字段,还有 sibling 字段,套用曾经学过的数据结构课程的术语就是使用长子-兄弟链表表示法表示一棵树的结构。
核心实现(省略了 lazyElement 相关代码)见下方代码,可以看到其中使用了很多函数。
deleteRemainingChildren(returnFiber, currentFirstChild):从 returnFiber 中删除 currentFirstChild 自身和它的右边的所有兄弟 fiberuseFiber(fiber, pendingProps):以 child.alternate 为蓝本,创建一个副本 fibercoerceRef(returnFiber, current, element): 解析 element 中的 ref 属性,不合法则 throw 各种类型的 errordeleteChild(returnFiber, childToDelete):从 returnFiber 中删除 childToDeletecreateFiberFromFragment:根据 Fragment 元素创建一个 fiber
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
lanes: Lanes,
): Fiber {
const key = element.key;
let child = currentFirstChild;
while (child !== null) {
if (child.key === key) {
const elementType = element.type;
if (elementType === REACT_FRAGMENT_TYPE) {
if (child.tag === Fragment) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props.children);
existing.return = returnFiber;
return existing;
}
} else {
if (
child.elementType === elementType
) {
deleteRemainingChildren(returnFiber, child.sibling);
const existing = useFiber(child, element.props);
existing.ref = coerceRef(returnFiber, child, element);
existing.return = returnFiber;
return existing;
}
}
// 没有匹配上
deleteRemainingChildren(returnFiber, child);
break;
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
}
if (element.type === REACT_FRAGMENT_TYPE) {
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
lanes,
element.key,
);
created.return = returnFiber;
return created;
} else {
const created = createFiberFromElement(element, returnFiber.mode, lanes);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
created.return = returnFiber;
return created;
}
}
reconcileSingleElement 的功能简单来说就是下面所述。
- 通过 child 和 sibling 遍历 returnFiber 的所有孩子 Fiber,检查 key 是否命中 element.key
- 删除 returnFiber 中没有命中 key 的 child,只保留命中的 child,通过 useFiber 创建一个新的 Fiber(如果 element 是一个 Fragment,则以 element.props.children 建立 Fiber),将returnFiber 赋给新的 fiber 的 return 字段,然后返回这个新的fiber。
- 如果 returnFiber 没有一个命中 key,则通过
createFiberFromElement或者createFiberFromFragment创建一个新的 fiber,这个fiber的key 就是 element.key,将 returnFiber 作为 新建 Fiber 的 return 字段,最后返回新建 Fiber
记得在被调用的时候 reconcileSingleElement 的返回值被 `placeSingleChild 包裹了,这个作用只是给返回的 fiber 更新一下 flags 字段。
function placeSingleChild(newFiber: Fiber): Fiber {
// This is simpler for the single child case. We only need to do a
// placement for inserting new children.
// Placement 标识表示当我们需要在原来的 dom 上插入新的 children 只需要做一个替换
if (shouldTrackSideEffects && newFiber.alternate === null) {
newFiber.flags |= Placement;
}
return newFiber;
}
reconcileSingleElement 讲完了,那就回到上一层的 reconcileChildFibers函数看看,发现如果 newChild 是一个数组,则会走reconcileChildrenArray逻辑
reconcileChildrenArray
这个函数是关于 newChildren 是数组时如何创建或更新 Fiber 的,比较复杂,下一篇文章会展开详细介绍,这里先讲一点点容易看懂的。
在其中有一个重要的函数调用叫 updateSlot,作用是根据 newChild 的内容更新对应的Fiber,逻辑如下:
如果 newChild 是一个number 或 string 类型的值,说明这是一个文本节点,则调 updateTextNode并返回
否则如果 newChild 是一个非空对象,则根据 newChild.$$typeof的值给 key 与 newChild.key 相同的 oldFiber 调 updateElement或 updatePortal或 updateSlot
如果 newChild 是一个数组或可迭代的对象,则检查 oldFiber 有没有 key,如果没有则返回 null,否则调 updateFragment