react——diff
DOM的Diffing算法
什么是虚拟DOM
当我们更新了state中的值的时候,React会去调用render()方法来重新渲染整个组件的UI,但是如果我们真的去操作这么大量的DOM,显然性能是堪忧的。所以React实现了一个Virtual DOM,组件的真实DOM结构和Virtual DOM之间有一个映射的关系,React在虚拟DOM上实现了一个diff算法,当render()去重新渲染组件的时候,diff会找到需要变更的DOM,然后再把修改更新到浏览器上面的真实DOM上,所以,React并不是渲染了整个DOM树,Virtual DOM就是JS数据结构,所以比原生的DOM快得多。
virtual dom 基本步骤
①用JS对象构建一颗虚拟DOM树,然后用虚拟树构建一颗真实的DOM树,然后插入到文档中。
②当状态变更时,重新构造一颗新的对象树,然后新树旧树进行比较,记录两树差异。
③把步骤2的差异应用到步骤1所构建的真实DOM树上,视图就更新了。
diff算法
其实React的 virtual dom的性能好也离不开它本身特殊的diff算法。传统的diff算法时间复杂度达到o(n3),而react的diff算法时间复杂度只是o(n),react的diff能减少到o(n)依靠的是react diff的三大策略。
传统diff 对比 react diff
传统的diff算法追求的是“完全”以及“最小”,而react diff则是放弃了这两种追求:
在传统的diff算法下,对比前后两个节点,如果发现节点改变了,会继续去比较节点的子节点,一层一层去对比。就这样循环递归去进行对比,复杂度就达到了o(n3),n是树的节点数,想象一下如果这棵树有1000个节点,我们得执行上十亿次比较,这种量级的对比次数,时间基本要用秒来做计数单位了。那么react究竟是如何把复杂度降低到o(n)的呢?
React diff 三大策略
策略一(tree diff):Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计。(DOM结构发生改变—–直接卸载并重新creat)
策略二(component diff):DOM结构一样—–不会卸载,但是会update
策略三(element diff):所有同一层级的子节点.他们都可以通过key来区分—–同时遵循1.2两点
虚拟DOM树分层比较(tree diff)
两棵树只会对同一层次的节点进行比较,忽略DOM节点跨层级的移动操作。React只会对相同颜色方框内的DOM节点进行比较,即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个DOM树的比较。由此一来,最直接的提升就是复杂度变为线型增长而不是原先的指数增长。
组件间的比较(component diff)
如果是同一个类型的组件,则按照原策略进行Virtual DOM比较。
如果不是同一类型的组件,则将其判断为dirty component,从而替换整个组价下的所有子节点。
如果是同一个类型的组件,有可能经过一轮Virtual DOM比较下来,并没有发生变化。如果我们能够提前确切知道这一点,那么就可以省下大量的diff运算时间。因此,React允许用户通过shouldComponentUpdate()来判断该组件是否需要进行diff算法分析。

如上图所示,当组件D变为组件G时,哪怕这两个组件结构相似,一旦React判断D和G是不用类型的组件,就不会比较两者的结构,而是直接删除组件D,重新创建组件G及其子节点。也就是说,如果当两个组件是不同类型但结构相似时,其实进行diff算法分析会影响性能,但是毕竟不同类型的组件存在相似DOM树的情况在实际开发过程中很少出现,因此这种极端因素很难在实际开发过程中造成重大影响。
元素间的比较(element diff)
当节点处于同一层级的时候,react diff 提供了三种节点操作:插入、删除、移动。
| 操作 | 描述 |
|---|---|
| 插入 | 新节点不存在于老集合当中,即全新的节点,就会执行插入操作 |
| 移动 | 新节点在老集合中存在,并且只做了位置上的更新,就会复用之前的节点,做移动操作(依赖于Key) |
| 删除 | 新节点在老集合中存在,但节点做出了更改不能直接复用,做出删除操作 |
经典面试题:
1.react/vue中的key有什么作用?(key的内部原理是什么)
2.为什么遍历列表时,key最好不用index
1.虚拟DOM中key的作用:
- 简单的说:key是虚拟DOM对象的标识,在更新显示时,key起着至关重要的作用
- 详细的说:当状态中的数据发生变化时,react会根据新数据生成新的虚拟DOM,随后react进行新虚拟DOM和旧虚拟DOM的diff对比,比较规则如下
- 旧虚拟 DOM中找到了与新虚拟DOM相同的key:
- 若虚拟DOM中内容没有变化,直接使用之前的真实DOM
- 若虚拟DOM中的内容变了,则生成真实DOM,随后替换掉页面之前的真实DOM
- 旧虚拟DOM中未找到与新虚拟DOM相同的key
- 根据数据创建新的真实DOM,随后渲染到页面
- 旧虚拟 DOM中找到了与新虚拟DOM相同的key:
2.用index作为key可能引发的问题
- 若对数据进行:逆序添加、逆序删除等破坏顺序的操作:会产生没有必要的真实DOM更新 ==> 页面没有问题,但是效率会很低
- 如果结构中还包含输入类DOM:会产生错误DOM == > 页面有问题
例子:
1 | class App extends Component{ |