React通过引入Virtual DOM的概念,极大地避免无效的Dom操作。将时间复杂度从O3降到了O1,归功于react的 diff策略。
实现原理:
- Web UI中DOM节点跨层级的移动操作特别少,可以忽略不计。
- 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
- 对于同一层级的一组子节点,它们可以通过唯一 id 进行区分。
在上面三个策略的基础上,React 分别将对应的tree diff、component diff 以及 element diff 进行算法优化,极大地提升了diff效率。
tree diff
React 对树的算法进行了简洁明了的优化,即对树进行分层比较,两棵树只会对同一层次的节点进行比较。相同层的元素,如果是同一类元素(同一种html标签),则不会销毁该dom,而会根据新的dom属性来更新旧的dom的属性,如果不是同一种标签则会直接销毁并且销毁其所有子节点,然后创建新的dom插入。
component diff
- 如果是同一类型的组件,按照原策略继续比较 Virtual DOM 树即可。
- 如果不是,则将该组件判断为 dirty component,从而替换整个组件下的所有子节点。
- 对于同一类型的组件,有可能其 Virtual DOM 没有任何变化,如果能够确切知道这点,那么就可以节省大量的 diff 运算时间。因此,React允许用户通过shouldComponentUpdate()来判断该组件是否需要进行diff算法分析,但是如果调用了forceUpdate方法,shouldComponentUpdate则失效。
element diff
当节点处于同一层级时,diff 提供了 3 种节点操作,分别为 INSERT_MARKUP (插入)、MOVE_EXISTING (移动)和 REMOVE_NODE (删除)。
相同的节点,仅仅是位置发生了变化,但却需要进行繁杂低效的删除、创建操作,其实只要对这些节点进行位置移动即可。React针对这一现象提出了一种优化策略:允许开发者对同一层级的同组子节点,添加唯一 key 进行区分
如果新旧dom树上的同一层级里存在相同key的dom则会直接实用原来的key的元素并且移动dom的位置,然后继续比对dom的类型和属性并更新,这会减少很多dom的创建和销毁,大部分情况下是可以提高性能的,但是在极端情况下并不会带来性能的提示,比如将最后一个元素提到第一位,react实际上是把所有前面的元素都挪了一遍位置,将它们移动到后面,如果不加key,react则只会更新最后一个dom的属性,反而得不偿失。所以在使用key的时候可以考虑下场景,是否真的能提高性能(99%的情况会提高,除非是很简单的一个基本标签,但这也就无所谓了,主要为了组件的key,防止组件频繁的创建和销毁,因为组件里往往有着比较深的下级dom,全部销毁和创建比较浪费性能,并且会触发组件的生命周期)