react
单节点
单节点更新
<div>1</div>
<div>2</div>
reconcileSingleElement 根据element useFiber新fiber
然后placeSingleChild返回新fiber
单节点新增
// 空
<div>2</div>
reconcileSingleElement 根据element createFiberFromElement新fiber
然后placeSingleChild返回新fiber
单节点删除
<div>1</div>
// 空
deleteRemainingChildren 将要删除的fiber以及sibling挂载到returnFiber.deletions
多节点
节点更新
<div>1</div>
<div>2</div>
<div>3</div>
<div>4</div>
oldFiber = currentFirstChild
nextOldFiber = oldFiber.sibling;
newFiber = updateSlot(returnFiber, oldFiber, newChildren[newIdx], lanes);
updateSlot 进入updateElement(returnFiber, oldFiber, newChild, lanes)
然后和单节点一样进行useFiber 返回新的fiber
节点新增
旧 A - B
新 A - B - C 新节点还没遍历完,旧fiber为null,需要在后面继续遍历新节点,并创建新的fiber跟在sibling
节点删除
<div>1</div>
<div>2</div>
<div>1</div>
新节点遍历完了,旧fiber还有,删掉旧fiber
节点移动/新增/删除
旧 A - B - C - D - E - F
新 A - B - D - G - C - E
首先将下标移动到oldFiber和 newChildren 不一致的地方,就是B的后面,计作lastPlacedIndex = 1,此时
旧: C - D - E - F
新: D - G - C - E
lastPlacedIndex往后遍历newChildren,找到其在旧fiber的位置3,因为3 > 1,D在右边不用动,lastPlacedIndex = 3
处理好的节点为A - B - D
还未处理的节点为C - E - F
继续遍历newChildren,找不到D,创建一个新的fiber D
继续遍历newChildren找到C,找到其在旧fiber的位置2, 因为2 < 3 C在D的左边要换到右边,fiber D 的sibling 指向 fiber C, lastPlacedIndex = 3
处理好的节点为A - B - D - C
还未处理的节点为E - F
继续遍历newChildren找到E,找到其在旧fiber的位置4,4 > 3,E在右边不用动,lastPlacedIndex = 4
处理好的节点为A - B - D - C - E
还未处理的节点为F
删掉F
总结
首先去掉相同部分,然后遍历新节点,根据新节点找到旧节点的位置赋值给一个指针,下一个节点的下标比指针大,说明在右边,不用动,否则在左边,需要移动,如果没找到,说明是新建的节点,直接往后插入。如果新节点遍历完还有剩下的,则说明旧节点多余了,需要删除。
react的diff只是针对 fiber 进行的处理,是一种简单的diff算法,react在reconcile之后进行completeWork标记workInProgress.flags |= Update,之后在commit进行dom的更新,先删再插入,插入按照sibling找到第一个没有被标记插入的dom为基准,before插入
vue
对于vue来说,diff的目的是oldnode对照newnode,将dom更新
react的这种简单diff,vue作出实现的话,就是对old fiber的操作转移到dom上面
由于简单diff在某个尾巴放在开头这种情况需要移动多次dom
因此出现了双端diff
双端diff
旧前......旧后
新前......新后
为了减少dom的移动,双端diff的比对方式是:先双端比较,再交叉比较
「新前 vs 旧前」、「新后 vs 旧后」、「新后 vs 旧前」——尾插、「新前 vs 旧后」——头插
移动过后,需要更新指针
如果双端都没有复用,就拿新节点找到在旧节点的位置,找到的话头插,旧节点置为undefined
如果没有找到,说明是新节点,直接原地插入
newStartIndex = newEndIndex 并且oldStartIndex > oldEndIndex
说明最后一个是新增
newStartIndex > newEndIndex 并且oldStartIndex <=oldEndIndex
说明有多余,直接删掉
快速diff
首尾一样的略过,只剩下不一样的开始处理
如果只剩下新节点,插入
如果只剩下旧节点,删除
这几步就是去掉了交叉的双端
如果新旧节点都有剩余,寻找最大子串,最大子串不用动,动其他的dom