reconcileChildren 调和单个节点
typescript
export function reconcileChildren(
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime,
) {
// current === null 说明是第一次渲染
if (current === null) {
workInProgress.child = mountChildFibers(
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
} else {
// 说明渲染过了 现在是更新
workInProgress.child = reconcileChildFibers(
workInProgress,
current.child,
nextChildren,
renderExpirationTime,
);
}
}typescript
export const reconcileChildFibers = ChildReconciler(true);
export const mountChildFibers = ChildReconciler(false);在这里,reconcileChildFibers和mountChildFibers都是调用的ChildReconciler,最后返回的函数都是reconcileChildFibers,ChildReconciler接受的布尔的参数 为 shouldTrackSideEffects(代表是否更新副作用)
在第一次mount的时候是不更新副作用的,只有当后续的更新才更新副作用
reconcileChildFibers
typescript
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
// 判断是否是一个Fragment组件
const isUnkeyedTopLevelFragment =
typeof newChild === 'object' &&
newChild !== null &&
newChild.type === REACT_FRAGMENT_TYPE &&
newChild.key === null;
// 如果是一个Fragment,直接渲染它的子组件就好了
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}
// Handle object types
const isObject = typeof newChild === 'object' && newChild !== null;
// 如果当前JSXElement是一个对象,说明是一个单独的jsx节点或者portal
if (isObject) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
case REACT_PORTAL_TYPE:
return placeSingleChild(
reconcileSinglePortal(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
),
);
}
}
// 说明是一个文字节点
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(
reconcileSingleTextNode(
returnFiber,
currentFirstChild,
'' + newChild,
expirationTime,
),
);
}
// 说明是多个节点
if (isArray(newChild)) {
return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}
// 是可迭代的节点
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
);
}
// 随后 如果currentFirstChild 是null,说明是更新了,子节点变成了null
// 就需要将老的子节点都删除
// Remaining cases are all treated as empty.
return deleteRemainingChildren(returnFiber, currentFirstChild);
}reconcileSingleElement
typescript
function reconcileSingleElement(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
element: ReactElement,
expirationTime: ExpirationTime,
): Fiber {
const key = element.key;
let child = currentFirstChild;
// 刚进到这里 如果child等于null,说明是第一次渲染
while (child !== null) {
// TODO: If key === null and child.key === null, then this only applies to
// the first item in the list.
if (child.key === key) {
// key相等的情况
if (
child.tag === Fragment
? element.type === REACT_FRAGMENT_TYPE
: child.elementType === element.type
) {
// key相等以及elementType都相等
// 说明当前节点是匹配的,兄弟节点是多余的 将多余的兄弟节点都删除
deleteRemainingChildren(returnFiber, child.sibling);
// 复用Fiber
const existing = useFiber(
child,
element.type === REACT_FRAGMENT_TYPE
? element.props.children
: element.props,
expirationTime,
);
existing.ref = coerceRef(returnFiber, child, element);
existing.return = returnFiber;
if (__DEV__) {
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
} else {
// key相同但是type不同,因为此时新的节点只有一个
// 说明都不能复用了,将所有的老的节点都删除
deleteRemainingChildren(returnFiber, child);
break;
}
} else {
// 如果key不相等,说明老的child在更新后不存在了,
// 需要删除,这里只是删除单个节点
deleteChild(returnFiber, child);
}
child = child.sibling;
}
if (element.type === REACT_FRAGMENT_TYPE) {
const created = createFiberFromFragment(
element.props.children,
returnFiber.mode,
expirationTime,
element.key,
);
created.return = returnFiber;
return created;
} else {
const created = createFiberFromElement(
element,
returnFiber.mode,
expirationTime,
);
created.ref = coerceRef(returnFiber, currentFirstChild, element);
created.return = returnFiber;
return created;
}
}reconcileSingleTextNode
typescript
function reconcileSingleTextNode(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
textContent: string,
expirationTime: ExpirationTime,
): Fiber {
// 这里只对比老的字节点的第一个是否为text节点 ,如果老的第一个也是text节点
if (currentFirstChild !== null && currentFirstChild.tag === HostText) {
// 将除text节点之外的兄弟节点都进行删除
deleteRemainingChildren(returnFiber, currentFirstChild.sibling);
// 直接复用当前Fiber节点
const existing = useFiber(currentFirstChild, textContent, expirationTime);
existing.return = returnFiber;
return existing;
}
// 如果老的节点的第一个不是text节点,则删除所有的节点,重新创建一个text节点
deleteRemainingChildren(returnFiber, currentFirstChild);
const created = createFiberFromText(
textContent,
returnFiber.mode,
expirationTime,
);
created.return = returnFiber;
return created;
}reconcileChildrenArray
当新节点为数组的情况
typescript
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
expirationTime: ExpirationTime,
): Fiber | null {
// 最终要返回的第一个Fiber节点,也就是链表结构的头节点
let resultingFirstChild: Fiber | null = null;
// 兄弟Fiber节点之间是链表结构,previousNewFiber代表的是链表的最后一个节点
let previousNewFiber: Fiber | null = null;
// 老的Fiber
let oldFiber = currentFirstChild;
// 最后一个放置的索引
let lastPlacedIndex = 0;
let newIdx = 0;
let nextOldFiber = null;
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
// oldFiber.index > newIdx 说明新老节点的顺序不一致了
if (oldFiber.index > newIdx) {
// 不一致的情况下 nextOldFiber 直接等于当前老的Fiber
nextOldFiber = oldFiber;
// 老的Fiber不能跟当前的newChildren不能复用了
oldFiber = null;
} else {
// 否则 等于老的Fiber的兄弟节点
nextOldFiber = oldFiber.sibling;
}
// updateSlot 对比新老节点的key是否相同,相同复用Fiber
// 不相同 不能复用,返回null 如果key相同,但是节点的类型不同,此时是新创建了Fiber的
const newFiber = updateSlot(
returnFiber,
oldFiber,
newChildren[newIdx],
expirationTime,
);
if (newFiber === null) {
if (oldFiber === null) {
oldFiber = nextOldFiber;
}
// 到这里跳出循环,找到第一个不能复用的Fiber节点 跳出循环,进行下面的操作
break;
}
if (shouldTrackSideEffects) {
// 在更新的时候, 如果newFiber.alternate === null 说明当前newFiber是新创建的
// 并没有复用oldFiber 那就删除老的Fiber
// 如果Fiber新老Fiber的key相同,但是节点类型不同,此时的newFiber就是新创建的
// newFiber.alternate 就是等于null
if (oldFiber && newFiber.alternate === null) {
deleteChild(returnFiber, oldFiber);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
// 将newFiber加入到resultingFirstChild的链表上
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
// oldFiber指向下一个,继续for循环查找第一个key不相同的Fiber节点
oldFiber = nextOldFiber;
}
if (newIdx === newChildren.length) {
// 新的Fiber已经都创建完了 把剩下的老的节点都删除
deleteRemainingChildren(returnFiber, oldFiber);
return resultingFirstChild;
}
if (oldFiber === null) {
// 如果老的fiber不存在 则直接遍历新的child,然后创建newChild的Fiber
for (; newIdx < newChildren.length; newIdx++) {
const newFiber = createChild(
returnFiber,
newChildren[newIdx],
expirationTime,
);
if (!newFiber) {
continue;
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
return resultingFirstChild;
}
// 走到这 说明老的Fiber还有,并且新的newChild还没有创建完
// 将老的Fiber存到一个map上,供下面匹配查找
const existingChildren = mapRemainingChildren(returnFiber, oldFiber);
for (; newIdx < newChildren.length; newIdx++) {
// 从map上,查找能复用的Fiber,没有就新创建
const newFiber = updateFromMap(
existingChildren,
returnFiber,
newIdx,
newChildren[newIdx],
expirationTime,
);
if (newFiber) {
if (shouldTrackSideEffects) {
if (newFiber.alternate !== null) {
// 如果newFiber.alternate不等于null,说明是复用的老的Fiber
// 那么就将map上的Fiber清除掉,因为已经复用了
existingChildren.delete(
newFiber.key === null ? newIdx : newFiber.key,
);
}
}
lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
// 将当前newFiber添加到链表上
if (previousNewFiber === null) {
resultingFirstChild = newFiber;
} else {
previousNewFiber.sibling = newFiber;
}
previousNewFiber = newFiber;
}
}
if (shouldTrackSideEffects) {
// newChild创建完了,如果老的Fiber还有,都删除掉
existingChildren.forEach(child => deleteChild(returnFiber, child));
}
return resultingFirstChild;
}总结一下:reconcileChildrenArray分为以下几个步骤
- 第一步,先遍历找到第一个不能复用的Fiber
- 第二步,如果newChild都创建完了,删除老的Fiber 节点
- 第三步,如果老的Fiber节点都复用完了,新的newChild还没创建完,直接遍历创建newChild的Fiber
- 第四步,当新老节点都有,将老的节点生成一个map结构,然后根据key或者index去map中查找能否复用
- 第五步, 如果newChild的Fiber都创建完了,但是老的Fiber还有,遍历将老的Fiber都删除
updateSlot
typescript
function updateSlot(
returnFiber: Fiber,
oldFiber: Fiber | null,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null {
// 获取key
const key = oldFiber !== null ? oldFiber.key : null;
// 说明新节点是一个文字节点
if (typeof newChild === 'string' || typeof newChild === 'number') {
// key不等于null 说明老的Fiber不是一个文本节点,但是新的节点是文本节点
// 说明当前Fiber不能复用,直接return null
if (key !== null) {
return null;
}
// key等于null,说明之前也是一个文本节点,直接复用文本节点的Fiber
return updateTextNode(
returnFiber,
oldFiber,
'' + newChild,
expirationTime,
);
}
// 根据key是否相等复用或者创建新的Fiber
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: {
if (newChild.key === key) {
if (newChild.type === REACT_FRAGMENT_TYPE) {
return updateFragment(
returnFiber,
oldFiber,
newChild.props.children,
expirationTime,
key,
);
}
return updateElement(
returnFiber,
oldFiber,
newChild,
expirationTime,
);
} else {
return null;
}
}
case REACT_PORTAL_TYPE: {
if (newChild.key === key) {
return updatePortal(
returnFiber,
oldFiber,
newChild,
expirationTime,
);
} else {
return null;
}
}
}
if (isArray(newChild) || getIteratorFn(newChild)) {
if (key !== null) {
return null;
}
return updateFragment(
returnFiber,
oldFiber,
newChild,
expirationTime,
null,
);
}
throwOnInvalidObjectType(returnFiber, newChild);
}
return null;
}mapRemainingChildren
创建oldFiber的map的过程
typescript
function mapRemainingChildren(
returnFiber: Fiber,
currentFirstChild: Fiber,
): Map<string | number, Fiber> {
const existingChildren: Map<string | number, Fiber> = new Map();
let existingChild = currentFirstChild;
while (existingChild !== null) {
if (existingChild.key !== null) {
existingChildren.set(existingChild.key, existingChild);
} else {
existingChildren.set(existingChild.index, existingChild);
}
existingChild = existingChild.sibling;
}
return existingChildren;
}deleteRemainingChildren
删除节点的方法
typescript
function deleteRemainingChildren(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
): null {
// shouldTrackSideEffects 在第一次得时候是false,第一次mount的时候不需要管
if (!shouldTrackSideEffects) {
// Noop.
return null;
}
// 逐个删除兄弟节点
let childToDelete = currentFirstChild;
while (childToDelete !== null) {
deleteChild(returnFiber, childToDelete);
childToDelete = childToDelete.sibling;
}
return null;
}deleteChild
typescript
function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {
// shouldTrackSideEffects 在第一次得时候是false,第一次mount的时候不需要管
if (!shouldTrackSideEffects) {
// Noop.
return;
}
// 将当前待删除的节点,放在父节点的effect连上
const last = returnFiber.lastEffect;
if (last !== null) {
last.nextEffect = childToDelete;
returnFiber.lastEffect = childToDelete;
} else {
returnFiber.firstEffect = returnFiber.lastEffect = childToDelete;
}
// 将待删除的节点的nextEffect置空,因为这个节点要被删除了,上面的副作用也没什么意义了
childToDelete.nextEffect = null;
// 打上待删除的标记 在后续commit阶段真正的删除
childToDelete.effectTag = Deletion;
}