react 和vue diff

85 阅读3分钟

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