reconcileChildren
主要作用是动态比较已缓存的虚拟 DOM 树与新的 element 对象,生成 workInProgress 的所有子节点,
没有缓存说明是新节点,会直接创建。
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
if (current === null) {
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
} else {
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
}
}
// reconcileChildrenFibers 和 mountChildFibers 来源
export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);
// shouldTrackSideEffects 标识, 是否为 Fiber 对象添加 effectTag
// true 添加 false 不添加
// 对于初始渲染来说, 只有根组件需要添加, 其他元素不需要添加, 防止过多的 DOM 操作
function ChildReconciler(shouldTrackSideEffects) {
...
return reconcileChildFibers;
}
reconcileChildFibers
先判断是否为 nextChild 是否为占位组件,如果是则将其子组件设置为 newChild。
const isUnkeyedTopLevelFragment =
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null;
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}
- 之后会根据
newChild的类型,执行不同的 reconcile 过程;
newChild 为对象
newChild.$$typeof === REACT_ELEMENT_TYPE
placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
newChild.$$typeof === REACT_PORTAL_TYPE
placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
lanes,
),
);
newChild.$$typeof === REACT_LAZY_TYPE
const payload = newChild._payload
const init = newChild._init
// 重新执行 reconcileChildFibers 函数
reconcileChildFibers(
returnFiber,
currentFirstChild,
init(payload),
lanes,
);
newChild 为数组
reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
newChild 为迭代器对象
reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChild,
lanes,
);
newChild 为 string 或者 number
placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
'' + newChild,
lanes,
),
);
reconcileSingleElement
- 循环遍历旧的子节点链表
currentFirstChild查找key与新的子节点child.key相似的节点; key相等,克隆复用currentFirstChild中当前节点,删除剩余节点;key不相等,删除currentFirstChild中当前节点,查看下一个兄弟节点;
let child = currentFirstChild;
while (child !== null) {
// child为旧的Fiber newChild为新的JSX element对象
if (child.key === newChild.key) {
// 克隆复用并返回旧节点
// 删除剩余节点
break
} else {
// 删除当前节点
}
child = child.sibling;
}
- 循环中未能找到相同
key的节点,因此需要创建一个新的节点并返回;
const created = createFiberFromFragment(
newChild.props.children,
returnFiber.mode,
lanes,
newChild.key
)
created.return = returnFiber
return created
reconcileChildrenArray
对数组做 reconcile,子 Fiber 链表(用 sibling 连接)节点称为 oldChild,子元素数组中的每一个 element 对象为 newChild。
- 在
oldChild不为 null 的前提下循环遍历newChildren数组; - 执行
updateSlot函数试图复用oldFiber,如果key相同则返回复用的oldFiber,如果key不同则返回 null,退出循环; - 到此步说明复用节点成功;如果不是首次渲染,则从父节点中删除此
oldFiber; - 将
newFiber插入到新子节点 Fiber 链表的最后; - 指定下一个
oldFiber继续循环;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
lanes
)
if (newFiber === null) break
if (shouldTrackSideEffects) deleteChild(returnFiber, oldFiber)
if (previousNewFiber === null) resultingFirstChild = newFiber;
else previousNewFiber.sibling = newFiber;
previousNewFiber = newFiber
oldFiber = oldFiber.index > newIdx ? oldFiber : oldFiber.sibling
}
- 如果
newIdx === newChildren.length,则删除剩余的oldFiber,返回resultingFirstChild; - 如果
oldFiber === null,继续循环遍历newChildren数组,为每一个新的 element 对象创建新的Fiber节点,并将这些newFiber也插入到新子节点 Fiber 链表的最后,返回resultingFirstChild。
if (newIdx === newChildren.length) {
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
if (oldFiber === null) {
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
if (newFiber === null) {
continue;
}
if (previousNewFiber === null) resultingFirstChild = newFiber;
else previousNewFiber.sibling = newFiber;
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
- 到此步骤说明
oldFiber和newChildren仍与剩余,因此,先为oldFiber创建一个 map,键为oldFiber的key或者index,值为oldFiber的本身; - 遍历剩余的
newChildren数组,根据key和index从 map 中查找可复用的 Fiber 节点,如果存在可复用newFiber,则将newFiber插入到新子节点 Fiber 链表的最后。
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = updateFromMap (
existingChildren,
returnFiber,
newIdx,
newChidren[newIdx],
lanes
)
if (newFiber !== null) {
if (previousNewFiber === null) resultingFirstChild = newFiber;
else previousNewFiber.sibling = newFiber;
previousNewFiber = newFiber
}
}
最终返回resultingFirstChild。