![44a33fdef4df979afad5621b544aec34.png](https://i-blog.csdnimg.cn/blog_migrate/910ce6a516e26d909db1e3e699ef6a37.jpeg)
说明:本篇文章参考来源如下,我只是进行了总结归纳,删繁就简,精心整理后,自我感觉可读性更强,更容易理解和看到diff的全貌。
twobin:React 源码剖析系列 - 不可思议的 react diffzhuanlan.zhihu.com![25c8df443b050f2e4284fc1ac633b628.png](https://i-blog.csdnimg.cn/blog_migrate/84cf572e8ad1c5f8b497e89f8b807a96.png)
1.传统diff算法
计算两颗树形结构差异的最优解一直是一个复杂且值得研究的问题,传统的diff算法,需要遍历整棵树的节点然后进行比较,是一个深度递归的过程,运算复杂度常常是O(n^3)
,这样的低效率在react中肯定是不能容忍的,那么react对于diff算法进行了哪些优化呢?
2.react diff的优化策略
- DOM节点跨层级的操作不做优化,因为很少这么做,这是针对的tree层级的策略;
- 对于同一个类的组件,会生成相似的树形结构,对于不同类的组件,生成不同的树形结构,这是针对conponent层级的策略;
- 对于同一级的子节点,拥有同层唯一的key值,来做删除、插入、移动的操作,这是针对element层级的策略;
3.详解tree策略
比较策略:
1.只会对相同层级的节点进行比较; 2.只有删除、创建操作,没有移动操作; 3.由于没做性能优化,所以官方建议少做这样的跨层级操作;
![54cf32a7f8e457f4c3a369f4bac24d1d.png](https://i-blog.csdnimg.cn/blog_migrate/343099d83e716c65f79f163986e736ff.png)
场景分析:
如图所示,react发现新树中,R节点下没有了A,那么直接删除A,在D节点下创建A以及下属节点。过程就是删除、创建,直接粗暴。
4.详解component策略
比较策略:
1.如果是同一个类的组件,则会继续往下diff运算; 2.如果不是一个类的组件,那么直接删除旧的,创建新的;
tips: 对于同一个类的组件,用户可以控制其不要进行diff运算,具体就是,用户可以使用
shouldComponentUpdate()
来告诉react要不要对此组件进行diff运算。
场景分析:
![fa51bffe63e06e30e0cf5a36786f50ba.png](https://i-blog.csdnimg.cn/blog_migrate/a2b133795b0829e6d674503084cb6fe0.png)
如图所示,当component D 换成了component G 后,即使两者的结构非常类似,但是react无视,骂骂咧咧的把component D删除后,重新创建了component G。
这也是合情合理的,因为基本不存在两个不同类但是组件结构相同的情况,如果有,只能证明使用者的代码需要优化,怎么会出现如此没有复用性的代码呢。
5.详解element策略
比较策略:
diff的时候,总是比较同一层级的节点们,这是前提,接下来提供了三种节点的操作:
- 插入:INSERT_MARKUP
- 删除:REMOVE_NODE
- 移动:MOVE_EXISTING
注意:每个节点都有在他们的层级唯一的key作为标识。
场景分析:
_mountIndex:标识节点在老集合中的位置; lastIndex:在diff过程中访问的index的最大值,一会会用到。
![52ceacc4cebd04c89a91451d0b61cf32.png](https://i-blog.csdnimg.cn/blog_migrate/28bb4fc976ee202b348288fea4c45acb.png)
对比开始,有一个优化的原则:如果当前节点在新集合中的位置比老集合中的位置靠前的话,是不会影响后续节点操作的,所以,这种情况就不用管,这个被对比的节点不会动。
具体到运算上,就是只要lastIndex < _mountIndex
,就不会动这个节点了。
这个lastIndex
可不是新集合中的位置,而是访问过的位置的最大值,每对比一轮就会变化的,只是个数学参照。知道了这个点,就很好推算了,下面开始:
- 节点B:此时lastIndex=0,_mountIndex=1;满足
lastIndex < _mountIndex
,因此B节点不动,此时lastIndex = Math.max(_mountIndex, lastIndex)
,就是1; - 节点A:此时lastIndex=1,_mountIndex=0;不满足
lastIndex < _mountIndex
,因此A节点进行移动操作,此时lastIndex = Math.max(_mountIndex, lastIndex)
,还是1; - 节点D:此时lastIndex=1,_mountIndex=3;满足
lastIndex < _mountIndex
,因此D节点不动,此时lastIndex = Math.max(_mountIndex, lastIndex)
,就是3; - 节点C:此时lastIndex=3,_mountIndex=2;不满足
lastIndex < _mountIndex
,因此C节点进行移动操作,当前已经比较完了
尽管ABCD节点都已经比较完了,但是diff过程还没完,还会整体遍历老集合中节点,看有没有没用到的节点,有的话,就删除。
待优化的部分:
![14cf95326f47ae7d30489a121008e19c.png](https://i-blog.csdnimg.cn/blog_migrate/ffe7706fdb44a91f6ae63b3eaab48efb.png)
如图所示,新集合中明明只有D节点进行了前置操作,别的节点顺序都没动,但是按照react的element diff的算法,仍然需要对所有的四个节点进行遍历计算,这是没有必要的,但是react也没有好的处理方式,所以官方建议:
尽量不要将一个节点从末尾提到最前边的类似的做法,会有性能损失。