两种节点类型
咱们能够从同级的节点数量将Diff分为两类:
当newChild类型为object、number、string,代表同级只有一个节点
当newChild类型为Array,同级有多个节点
在接下来两节咱们会别离探讨这两类节点的Diff,留神这里的单节点是指虚构dom节点是个单或者多节点,能够简略看做是不是返回的数组
单节点
单节点比拟还是比较简单的
//删除节点 function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void { if (!shouldTrackSideEffects) { // Noop. return; }//effect链的解决 const last = returnFiber.lastEffect; if (last !== null) { last.nextEffect = childToDelete; returnFiber.lastEffect = childToDelete; } else { //证实临时还没有造成链须要第一个节点 returnFiber.firstEffect = returnFiber.lastEffect = childToDelete; } const deletions = returnFiber.deletions; if (deletions === null) { returnFiber.deletions = [childToDelete]; // TODO (effects) Rename this to better reflect its new usage (e.g. ChildDeletions) returnFiber.effectTag |= Deletion; } else { deletions.push(childToDelete); } childToDelete.nextEffect = null; }//批量删除节点的工具函数(更精确的是批量标记) function deleteRemainingChildren( returnFiber: Fiber, currentFirstChild: Fiber | null, ): null { if (!shouldTrackSideEffects) { // Noop. return null; } // TODO: For the shouldClone case, this could be micro-optimized a bit by // assuming that after the first child we've already added everything. let childToDelete = currentFirstChild; while (childToDelete !== null) { deleteChild(returnFiber, childToDelete); childToDelete = childToDelete.sibling; } return null; }//element其实就是新的虚构dom function reconcileSingleElement( returnFiber: Fiber, currentFirstChild: Fiber | null, element: ReactElement): Fiber { const key = element.key; let child = currentFirstChild; // 首先判断是否存在对应DOM节点 while (child !== null) { // 上一次更新存在DOM节点,接下来判断是否可复用 // 首先比拟key是否雷同 if (child.key === key) { // key雷同,接下来比拟type是否雷同 switch (child.tag) { // ...省略case default: { if (child.elementType === element.type) { // type雷同则示意能够复用 deleteRemainingChildren(returnFiber, child.sibling);//显然这个节点的后续节点都必须删除了 因为找到了 const existing = useFiber(child, element.props);//useFiber故名思义 这里的element.props就是后续看是否要调整的属性 // 返回复用的fiber return existing; } // type不同则跳出switch break; } } // 代码执行到这里代表:key雷同然而type不同 // 将该fiber及其兄弟fiber标记为删除 deleteRemainingChildren(returnFiber, child); break; } else { // key不同,将该fiber标记为删除 deleteChild(returnFiber, child); } child = child.sibling; } // 创立新Fiber,并返回 ...省略}
能够发现须要被删除的fiber 不会在这间接真的删除,而是造成一个effect链,另外父节点会保护一个deletions的fiber数组
首先判断child是否存在,不存在则间接开始兄弟节点的比拟,while终止在同层比拟实现后
几种逻辑分支
- key雷同,类型也雷同间接可复用,后续就看属性状况更新属性即可
- key雷同,类型不同了,间接deleteRemainingChildren 删除这个节点及他的兄弟节点,这里是因为key雷同了,后续没有持续比拟找可复用节点的意义了,故把原节点删完就能够了
- key不同,间接把这个比拟的节点删除
多节点
先整顿一下源码
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; for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) { if (oldFiber.index > newIdx) { nextOldFiber = oldFiber; oldFiber = null; } else { nextOldFiber = oldFiber.sibling; } const newFiber = updateSlot( returnFiber, oldFiber, newChildren[newIdx], lanes, ); if (newFiber === null) { 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); if (previousNewFiber === null) { //构建新的fiber链作为返回值 // TODO: Move out of the loop. This only happens for the first run. resultingFirstChild = newFiber; } else { previousNewFiber.sibling = newFiber; } previousNewFiber = newFiber; oldFiber = nextOldFiber; } if (newIdx === newChildren.length) { // We've reached the end of the new children. We can delete the rest. deleteRemainingChildren(returnFiber, oldFiber); 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], lanes); if (newFiber === null) { continue; } lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx); if (previousNewFiber === null) {//构建新的fiber链作为返回值 // 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. const existingChildren = mapRemainingChildren(returnFiber, oldFiber); // Keep scanning and use the map to restore deleted items as moves. for (; newIdx < newChildren.length; newIdx++) { const newFiber = updateFromMap( existingChildren, returnFiber, newIdx, newChildren[newIdx], lanes, ); if (newFiber !== null) { if (shouldTrackSideEffects) { if (newFiber.alternate !== null) { 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; }
先对几个用到的重要函数解读一下
updateSlot这个函数能够简略了解为节点比拟,如果不匹配返回null,不然就是一个可复用的fiber节点
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; }
mapRemainingChildren返回一个children形成的Map key为id或者是child的index
内容比拟多倡议分成三个步骤去看
第一个循环,比拟newChildren 和oldFiber和他的兄弟们 会有三种状况
- key不同循环间接进行
- key雷同,类型不同,fiber标记为删除循环持续 i++ oldFiber = nextOldFiber;
- 循环完结newChildren或者是oldFiber和他的兄弟们遍历完结了
- 循环完解决一下几种状况 ,一种是newChildren现遍历完了,那删除残余的oldFiber,deleteRemainingChildren(returnFiber, oldFiber); 第二种是oldFiber遍历完了,那残余的newChildren 须要创立fiber节点 并且拼接在previousNewFiber这个后果链上 触发这两种状况都会退出整个diff
- 也就是都没有遍历完,状况就是因为节点地位挪动导致的,这个时候先要mapRemainingChildren(returnFiber, oldFiber);把残余的fiber做一个Map映射,而后newChildren 残余的节点去Map中查找,重点是placeChild函数
function placeChild( newFiber: Fiber, lastPlacedIndex: number, newIndex: number, ): number { newFiber.index = newIndex; if (!shouldTrackSideEffects) { // Noop. return lastPlacedIndex; } const current = newFiber.alternate; if (current !== null) { const oldIndex = current.index; if (oldIndex < lastPlacedIndex) { // This is a move. //本来节点的index比以后最近一次替换过的节点的index还小的话标记为挪动,且lastPlacedIndex不变 newFiber.effectTag = Placement; return lastPlacedIndex; } else { // This item can stay in place. //返回原有节点的地位作为新的lastPlacedIndex return oldIndex; } } else { // This is an insertion. //newChildren在本来没有 齐全是新建的 newFiber.effectTag = Placement; return lastPlacedIndex; } }
举个例子如果 01234要变为12304 假如lastPlacedIndex为0初始开始循环
1 oldIndex为1 oldIndex > lastPlacedIndex 不动 lastPlacedIndex = oldIndex也就是1
2 oldIndex为2 oldIndex > lastPlacedIndex 不动 lastPlacedIndex = oldIndex也就是2
3 oldIndex为3 oldIndex > lastPlacedIndex 不动 lastPlacedIndex = oldIndex也就是3
0 oldIndex为0 oldIndex < lastPlacedIndex 标记挪动 lastPlacedIndex 不变还是3
4 oldIndex为4 oldIndex > lastPlacedIndex 不动 lastPlacedIndex 改为4