diff算法作为Virtual DOM的加速器,其算法的改进优化是React整个界面渲染的基础和性能的保障,同时也是React源码中最神秘的,最不可思议的部分。
传统diff、React diff
传统diff算法:
- 通过循环递归对节点进行依次对比,算法复杂度达到 O(n^3)
- 如果要展示1000个节点,得执行上亿次比较。。即便是CPU快能执行30亿条命令,也很难在一秒内计算出差异。
React diff:
- React15对传统的diff做了一些限制,使得时间复杂度变为了O(n)。
- 调和:将Virtual DOM树转换成actual DOM树的最少操作的过程
- diff算法:调和的具体实现。
- react的 diff 能减少到o(n)依靠的是react diff的三大策略
tree diff component diff element diff 。
React 三大策略
tree diff:
相同层级:
- 首先,进行同级比较,并非循环比较。
- 这样比较次数就降为一层一次,时间复杂度直接降为O(n)
- 如果同级相同位置节点不一样,则直接删除替换,简单粗暴。
跨层级:
createA-->createB-->createC-->deleteA
component diff:
- component diff相当于是子树的diff,基本方案和tree diff是一致的
- 如果如下图D变为G,那么直接删除D这一整棵树,然后重新渲染G树。
element diff:
对于同一节点的元素,diff算法提供了三种操作:插入、移动、删除。分别为INSERT_MARKUP(插入) ,MOVE_EXISTING(移动) ,REMOVE_NODE(删除) 。
INSERT_MARKUP :新的组件类型不在旧集合中,即全新的节点,需要对新节点进行插入操作。MOVE_EXISTING :旧集合中有新组件类型,且element是可更新的类型,这时候就需要做移动操作,可以复用以前的DOM节点。REMOVE_NODE :旧组件类型,在新集合里也有,但对应的element不同则不能直接复用和更新,需要执行删除操作,或者旧组件不在新集合里的,也需要执行删除操作。
React中key的引入、性能的提升
未引入key之前的操作:
- 老集合中包含节点:A、B、C、D
- 更新后的新集合中包含节点:B、A、D、C
- 此时新老集合进行 diff 差异化对比,发现 B != A,则创建并插入 B 至新集合,删除老集合 A;以此类推,创建并插入 A、D 和 C,删除 B、C 和 D。
- 都是相同的节点,但由于位置发生变化,导致需要进行繁杂低效的删除、创建操作,其实只要对这些节点进行位置移动即可。
引入key之后的操作:
- 此时的操作,是B、D不做任何操作,AC移动到相应位置【前提是都有相同的唯一key】
- 如果,此时的key不相同,全都发生了变化,那么节点全都是要删除重新构建,将会消耗大量性能。
虚拟DOM中key的作用:
- key是虚拟DOM对象的标识,在更新显示时key起着极为重要的作用。
- 当状态中的数据发生变化时,React会根据新数据 生成新的虚拟DOM,随后React进行新虚拟DOM与旧虚拟DOM的diff比较,比较规则如下:
- 1、 旧虚拟DOM中找到与新虚拟DOM相同的key:
- 若虚拟DOM中内容没变, 直接使用之前的真实DOM
- 若虚拟DOM中内容变了,则生成新的真实DOM,随后替换掉页面中之前的真实DOM
- 2、 旧虚拟DOM中未找到与新虚拟DOM相同的key:
|