function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime,
) {
// 首次渲染
if (current === null) {
// If this is a fresh new component that hasn't been rendered yet, we
// won't update its child set by applying minimal side-effects. Instead,
// we will add them all to the child before it gets rendered. That means
// we can optimize this reconciliation pass by not tracking side-effects.
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
} else {
// If the current child is the same as the work in progress, it means that
// we haven't yet started any work on these children. Therefore, we use
// the clone algorithm to create a copy of all the current children.
// If we had any progressed work already, that is invalid at this point so
// let's throw it out.
// 更新渲染
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child, // 再次渲染时才会有子节点
nextChildren,
renderExpirationTime,
);
}
}
// 更新渲染
export const reconcileChildFibers = ChildReconciler(true);
// 首次渲染
export const mountChildFibers = ChildReconciler(false);
reconcileChildFibers
reconcileChildFibers 是 ChildReconciler 最终返回的函数
先判断 newChild 是不是 Fragment 节点,如果是 Fragment 则将 newChild 赋值为 newChild.props.children
接着判断 newChild 是不是 isObject
REACT_ELEMENT_TYPE:reconcileSingleElement
REACT_PORTAL_TYPE: reconcileSinglePortal
判断 string or number:reconcileSingleTextNode
判断 isArray:reconcileChildrenArray
判断是 iterator 函数: reconcileChildrenIterator
都不符合以上情况,并且还是 isObject,则抛出错误
newChild 为 undefined 并且非 Fragment,提醒没有返回值
为 null 的情况,新的 props 的 children 为 null,则把现有的所有 children 都删掉
// 上一次渲染完成后第一个 child 节点, current.child
let oldFiber = currentFirstChild;
let lastPlacedIndex = 0;
let newIdx = 0;
// 下一个老的节点
let nextOldFiber = null;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
// 老的 fiber 的 index 位置与新的不同,位置不匹配
if (oldFiber.index > newIdx) {
// 将 oldFiber 赋值给 nextOldFiber 暂存
nextOldFiber = oldFiber;
oldFiber = null;
} else {
// 没变化则下一个老的节点为当前的节点的兄弟节点
nextOldFiber = oldFiber.sibling;
}
//
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
expirationTime,
);
// key 相同根据类型确定复用类型
// 文本节点没有key,为 null 表示不能复用, 能复用也有复用复用节点和新建节点的区分
if (newFiber === null) {
// TODO: This breaks on empty slots like null children. That's
// unfortunate because it triggers the slow path all the time. We need
// a better way to communicate whether this was a miss or null,
// boolean, undefined, etc.
// 重置 oldFiber,跳出循环,即找到不能复用的那个节点为止的fiber,那么 newIdx 则表示有多少个节点相同的那个index
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);
// previousNewFiber 中间变量,将数组构建成链表结构
if (previousNewFiber === null) {
// TODO: Move out of the loop. This only happens for the first run.
// 首个子节点
resultingFirstChild = newFiber;
} else {
// TODO: Defer siblings if we're not at the right index for this slot.
// I.e. if we had null values before, then we want to defer this
// for each null value. However, we also don't want to call updateSlot
// with the previous one.
// 构建链表结构
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
oldFiber = nextOldFiber;
}
// 新数组操作完,所有新的 children 都全部创建 fiber 节点了
if (newIdx === newChildren.length) {
// We've reached the end of the new children. We can delete the rest.
// 删除老的
deleteRemainingChildren(returnFiber, oldFiber);
// 返回首个子节点,其他节点是通过 sibling 指向下去
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],
expirationTime,
);
if (!newFiber) {
continue;
}
// 同样 placeChild 标记移动位置
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
// 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.
// newChildren 还没创建完,oldFiber 还不为null
// 数组存在顺序的变化,在 oldFiber 里找到可以复用的,通过 map 快速寻找
// 根据 key 或 index 创建 map 对象
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// 一次完整的遍历
// Keep scanning and use the map to restore deleted items as moves.
for (; newIdx < newChildren.length; newIdx++) {
// 根据 map 找可以复用的节点 或 创建新节点,跟 updateSlot 相似
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
expirationTime,
);
if (newFiber) {
if (shouldTrackSideEffects) {
// 复用的情况
if (newFiber.alternate !== null) {
// The new fiber is a work in progress, but if there exists a
// current, that means that we reused the fiber. We need to delete
// it from the child list so that we don't add it to the deletion
// list.
// 从 map 里删除
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));
}
函数式组件(FunctionComponent)更新过程
updateFunctionComponent
reconcileChildren
fiber.children
生成fiber
子树Fiber
对象是否可以复用,在第一次渲染就渲染了fiber
子树,state
变化可能会导致某些子节点产生变化而不能复用,但是大部分是可以复用的key
优化reconcileChildFibers
reconcileChildFibers
是ChildReconciler
最终返回的函数newChild
是不是Fragment
节点,如果是Fragment
则将newChild
赋值为newChild.props.children
newChild
是不是isObject
REACT_ELEMENT_TYPE
:reconcileSingleElement
REACT_PORTAL_TYPE
:reconcileSinglePortal
string or number
:reconcileSingleTextNode
isArray
:reconcileChildrenArray
iterato
r 函数:reconcileChildrenIterator
isObject
,则抛出错误newChild
为undefined
并且非Fragment
,提醒没有返回值null
的情况,新的props
的children
为null
,则把现有的所有children
都删掉React Element 类型——reconcileSingleElement
elementType
来创建新的节点useFiber 复用节点
deleteChild 标记删除 和 deleteRemainingChildren
数组类型——reconcileChildrenArray
尽量减少数组的遍历次数来达到判断节点是否可复用的过程
第一次遍历
newIdx
则为能够有几个能够复用的节点的index
updateSlot
中通过判断新老Key
是否相同来复用updateSlot
返回null
表示不能复用, 直接break
oldFiber
有key
,而newChild
是textNode
直接返回null
, 因为文本节点是没有Key
的textNode、Fragment、Element
都会判断oldFiber
不为null
就复用,为null
则创建新的第一次遍历完 或者
break
后newIdx
等于newChildren.length
说明已经在updateSlot
中创建新的对象了, 新数组操作完成了, 所有新节点都已经创建oldFiber
oldFiber
为null
,老的节点被复用完了,遍历到最后没有能复用的节点了,新的还有部分没创建, 则直接创建新的最后
newChildren
还没创建完,oldFiber
还不为null
的情况,数组存在顺序的变化updateFromMap
: 根据key
或index
创建map
对象,通过map
对象在oldFiber
里找到可以复用的或者创建新的// 上一次渲染完成后第一个 child 节点, current.child let oldFiber = currentFirstChild; let lastPlacedIndex = 0; let newIdx = 0; // 下一个老的节点 let nextOldFiber = null; for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) { // 老的 fiber 的 index 位置与新的不同,位置不匹配 if (oldFiber.index > newIdx) { // 将 oldFiber 赋值给 nextOldFiber 暂存 nextOldFiber = oldFiber; oldFiber = null; } else { // 没变化则下一个老的节点为当前的节点的兄弟节点 nextOldFiber = oldFiber.sibling; } // const newFiber = updateSlot( returnFiber, oldFiber, newChildren[newIdx], expirationTime, ); // key 相同根据类型确定复用类型 // 文本节点没有key,为 null 表示不能复用, 能复用也有复用复用节点和新建节点的区分 if (newFiber === null) { // TODO: This breaks on empty slots like null children. That's // unfortunate because it triggers the slow path all the time. We need // a better way to communicate whether this was a miss or null, // boolean, undefined, etc. // 重置 oldFiber,跳出循环,即找到不能复用的那个节点为止的fiber,那么 newIdx 则表示有多少个节点相同的那个index 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); // previousNewFiber 中间变量,将数组构建成链表结构 if (previousNewFiber === null) { // TODO: Move out of the loop. This only happens for the first run. // 首个子节点 resultingFirstChild = newFiber; } else { // TODO: Defer siblings if we're not at the right index for this slot. // I.e. if we had null values before, then we want to defer this // for each null value. However, we also don't want to call updateSlot // with the previous one. // 构建链表结构 previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; oldFiber = nextOldFiber; } // 新数组操作完,所有新的 children 都全部创建 fiber 节点了 if (newIdx === newChildren.length) { // We've reached the end of the new children. We can delete the rest. // 删除老的 deleteRemainingChildren(returnFiber, oldFiber); // 返回首个子节点,其他节点是通过 sibling 指向下去 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], expirationTime, ); if (!newFiber) { continue; } // 同样 placeChild 标记移动位置 lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (previousNewFiber === null) { // 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. // newChildren 还没创建完,oldFiber 还不为null // 数组存在顺序的变化,在 oldFiber 里找到可以复用的,通过 map 快速寻找 // 根据 key 或 index 创建 map 对象 const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
// 一次完整的遍历 // Keep scanning and use the map to restore deleted items as moves. for (; newIdx < newChildren.length; newIdx++) { // 根据 map 找可以复用的节点 或 创建新节点,跟 updateSlot 相似 const newFiber = updateFromMap( existingChildren, returnFiber, newIdx, newChildren[newIdx], expirationTime, ); if (newFiber) { if (shouldTrackSideEffects) { // 复用的情况 if (newFiber.alternate !== null) { // The new fiber is a work in progress, but if there exists a // current, that means that we reused the fiber. We need to delete // it from the child list so that we don't add it to the deletion // list. // 从 map 里删除 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; }