解析React Diff 算法

Diff 的做用

React Diff 会帮助咱们计算出 Virtual DOM 中真正发生变化的部分,而且只针对该部分进行实际的DOM操做,而不是对整个页面进行从新渲染html

传统diff算法的问题

传统的diff算法是使用循环递归对节点进行依次对比,复杂度为O(n^3),效率低下。react

React diff算法策略

  • 针对树结构(tree diff):对UI层的DOM节点跨层级的操做进行忽略。(数量少)
  • 针对组件结构(component diff):拥有相同的两个组件生成类似的树形结构,拥有不一样的两个组件会生成不一样的属性结构。
  • 针对元素结构(element-diff): 对于同一层级的一组节点,使用具备惟一性的id区分 (key属性)

tree diff 的特色

  • React 经过使用 updateDepth 对 虚拟DOM树进行层次遍历算法

  • 两棵树只对同一层级节点进行比较,只要该节点不存在了,那么该节点与其全部子节点会被彻底删除,不在进行进一步比较。性能

  • 只须要遍历一次,便完成对整个DOM树的比较。优化

React diff 只考虑同层次节点位置变换,若为跨层级的位置变化,则是建立节点和删除节点的操做。即在新位置上从新建立相同的节点,而删除原位置的节点。3d

Tips: React 官方建议不要进行DOM节点的跨层级操做,但是经过CSS来隐藏,显示节点,而不是真正地删除和添加DOM节点,保持稳定的DOM结构会对性能提高有帮助。code

component diff的特色

  • 同一类型的组件,按照原策略(tree diff)比较 virtual DOM tree
  • 同类型组件,组件A转化为了组件B,若是virtual DOM 无变化,能够经过shouldComponentUpdate()方法来判断是否

React官方文档对于 shouldComponentUpdate的介绍component

  • 不一样类型的组件,那么diff算法会把要改变的组件判断为dirty component,从而替换整个组件的全部节点。

就算结构再类似的组件,只要 React 判断是不一样的组件,就不会判断是否为不一样类型的组件,就不会比较其结构,而是删除组件以及其子组件,并建立新的组件以及其子节点。orm

element diff特色

对于处于同一层级的节点,React diff 提供了三种节点操做: 插入,移动,删除cdn

  • 插入: 新的组件不在原来的集合中,而是全新的节点,则对集合进行插入操做。
  • 删除:组件已经在集合中,但集合已经更新,此时节点就须要删除。
  • 移动:组件已经存在于集合中,而且集合更新时,组件并无发生更新,只是位置发生改变,例如:(A,B,C,D) → (A,D,B,C), 若是为传统diff则会在检测到旧集合中第二位为B,新集合第二位为D时删除B,插入D,而且后面的全部节点都要从新加载,而 React diff 则是经过向同一层的节点添加 惟一key 进行区分,而且移动。

一些移动的场景与逻辑

节点相同,位置不一样

nT73TS.md.jpg

按新集合中顺序开始遍历

  1. B在新集合中 lastIndex(相似浮标) = 0, 在旧集合中 index = 1,index > lastIndex 就认为 B 对于集合中其余元素位置无影响,不进行移动,以后lastIndex = max(index, lastIndex) = 1
  2. A在旧集合中 index = 0, 此时 lastIndex = 1, 知足 index < lastIndex, 则对A进行移动操做,此时lastIndex = max(Index, lastIndex) = 1
  3. D和B操做相同,同(1),不进行移动,此时lastIndex=max(index, lastIndex) = 3
  4. C和A操做相同,同(2),进行移动,此时lastIndex = max(index, lastIndex) = 3
节点位置均有变化

nT7GFg.md.jpg

1.同上面那种情形,B不进行移动,lastIndex=1

2.新集合中取得E,发现旧中不存在E,在 lastIndex处建立E,lastIndex++

3.在旧集合中取到C,C不移动,lastIndex=2

4.在旧集合中取到A,A移动到新集合中的位置,lastIndex=2

5.完成新集合中全部节点diff后,对旧集合进行循环遍历,寻找新集合中不存在但就集合中的节点(此例中为D),删除D节点。

React diff 的不足之处

nT71w8.md.jpg

此例中D直接从最后一位提高至第一位,致使lastIndex在第一步直接提高为3,使ABC在进行index与lastIndex的判断时均处于 index < lastIndex 的状况,使ABC都须要作移动操做。因此咱们应该减小将最后一个节点提高至第一个的操做,若是操做频率较大或者节点数量较多时,会对渲染性能产生影响。

小结

  • React diff 与 传统diff 的不一样是 React经过优化将O(n^3)提高至O(n)
  • React 经过三个方面对tree diff, component diff, element diff 进行了优化
  • 在开发时,尽可能保持稳定的DOM结构,而且减小将最后的节点移动到首部的操做,可以优化渲染性能。

参考资料

《深刻React技术栈》

React 源码剖析系列 - 难以想象的 react diff

相关文章
相关标签/搜索