react的diff算法讲解

传统的diff算法
通过循环递归进行依次对比,算法复杂度高达O(n^3),n代表树的节点数,这个算法有多么庞大?如果要展示1000个节点就要执行上亿次比较,你电脑CPU再好也很难在1秒内计算出差异。
react的diff算法

  • 先了解一下什么是调和?
    将virtual DOM(虚拟dom)转换成actual DOM(真实dom)的 最少操作过程就叫调和,简单理解就是简化算法复杂度

  • react的diff算法
    react的diff就是实现了上述的调和,简化了算法的复杂度

react的三大diff策略

react用tree diff(树比较)、component diff(组件比较)、element diff(元素比较)三大策略将O(n^3)的复杂度转化为O(n)的复杂度

策略一(tree diff):
dom树比较,跨层级比较时候只会进行删除或者创建节点操作,这种操作比较少,可以忽略不计

  1. react通过updateDepth对virtual DOM树进行层级控制
  2. 树分层进行比较,两棵树只对同一层的节点进行比较,如果节点不存在,则删除这个节点以及此节点下所有子节点,不会再进一步比较
  3. 只需遍历一次,就能完成整棵DOM树的比较
    react的diff算法讲解_第1张图片
    上图以A为根节点的整棵树都会被重新创建,而不是移动,因为官方不建议进行DOM的跨层级操作,可以通过CSS的显隐来实现,而不是真正的移除、添加DOM节点

策略二(component diff):
react对不同组件间的比较,有两类策略:

  1. 组件变化时,如果 virtual dom 没变,可通过 shouldComponentUpdate 方法控制是否需要 diff 运算
  2. 组件类不同(结构相似),则判断为 dirty component(脏组件),整个替换

注意:如果组件A和组件B结构相似,但react判断不是同类型组件,则不会比较结构,而是直接删除一个节点及其子节点,然后创建新的组件及其子节点代替

策略三(element diff):

当节点处于同一层级时,提供三种节点操作:删除、插入、移动
插入:
组件C不在集合(A、B)中,需要插入

删除:
①组件C在集合(A、B、C)中,但是C节点已经修改,不能复用和更新,所以需要删除旧的 C,创建新的
②组件C之前在集合(A、B、C)中,但是集合变成新的集合(A、B)了,C就需要被删除

移动:
组件D已经在集合(A、B、C、D)中了,并且集合更新时,D并没有发生更新,只是位置进行了改变,变成了新的集合(A、D、B、C),D在第二个,不需要像传统diff一样,让旧集合的第二个B和新集合的第二个D比较,并且删除第二个位置的B,再在第二个位置插入D,而是对同一层级的同组子节点添加唯一的Key进行区分,移动即可

下面重点说一下移动的逻辑

在将移动前,先跟大家了解一些概念

  • 在react的diff中,移动的逻辑是从新集合中拿到的值去和旧集合比较,然后获取到旧集合中元素的index,与当前的lastIndex进行比较,如果index < lastIndex就将旧集合中的这个元素进行移动
  • lastIndex可以看做是一个标识,也就是一个map的索引,最开始默认为0,它会与map中的元素进行比较,然后取(index, lastIndex)中的最大值
  • 简单来说就是用新集合与旧集合进行比较,然后按照以新集合为标准,去调整旧集合,来向新集合靠拢

场景一:新旧集合中存在相同节点但是位置不同时,如何移动节点
react的diff算法讲解_第2张图片
第一步: 先从新集合中取到第一个元素B,然后判断旧集合中是否存在元素B,发现存在后,就去判断是否移动B元素,B元素在旧集合中index为1,此时lastIndex为0,不满足 index < lastIndex条件,因此B不做移动操作,完事后更新lastIndex为(index, lastIndex)中较大值=1;

第二步: 然后在新集合中取第二个元素A,同样判断是否存在,A在旧集合中的index为0,这是lastIndex为1,满足index < lastIndex,所以对A元素进行移动操作,此时lastIndex取(index, lastIndex)较大值=1;

第三步: 新集合中取第三个元素D,D在旧集合中index为3,此时lastIndex为1,不满足条件,更新lastIndex=3;

第四步: 新集合中取到第四个元素C,C在旧集合中index为2,满足index < lastIndex,移动旧集合中的C元素;


场景二:新集合中有新加入的节点,旧集合中有需要删除的节点
react的diff算法讲解_第3张图片

第一步: 在新集合中取到B后再去旧集合中比较,在旧集合中B的index为1,lastIndex初始默认为0,在旧集合中B节点不满足index < lastIndex 所以不进行移动,并更新lastIndex为1;

第二步: 在新集合中取到E,发现在旧集合中并没有,所以在lastIndex为1的位置创建E,并更新lastIndex为1;

第三步: 在新集合中取到C,再去旧集合中寻找C,发现C在旧集合中index为2,此时lastIndex为1,不满足index < lastIndex,不移动C,并更新lastIndex为2;

第四步: 在新集合中取到A,然后再去旧集合中寻找A,发现A在旧集合中index为0,此时lastIndex为2,满足index < lastIndex,将旧集合中的A移动到末尾处,更新lastIndex为2;

第五步: 新集合对比后,再对旧集合进行遍历,判断新集合没有,但是旧集合有的元素D,发现D,删除D,diff操作结束

diff算法不足和待优化

react的diff算法讲解_第4张图片

上图diff过程:
第一步: 取新集合中的D,在旧集合中D的index为3,lastIndex为初始值0,不满足index < lastIndex 不移动D,并且更新lastIndex为3;

后续步骤 中因为index都满足index < lastIndex,所以除了D元素外,其余元素都需要移动;

我们认为只移动D元素至最后就好了,不必移动A、B、C元素。没错,这就是现在diff算法的不足,在开发中我们尽量减少将最后一个节点移动到首部的操作,节点数过大或者操作频繁时,会影响react的渲染性能

你可能感兴趣的:(react)