一起来学React吧(2018.7.11——6)
React的diff算法
学习了一周多的React,看了不少资料,基本上都在称赞它的虚拟Dom和牛掰的diff算法。今天花了大半天的时间看了diff相关的资料,这里整理了一点记录一下。
再说React的diff算法之前,得先知道传统的diff是什么。传统的diff算法是用来转化树结构的,他要计算将一棵树转化为另一个不同结构树的步骤,并且实现转化。传统的diff做法就是,每个节点逐一比较。循环遍历所有的节点,执行插入,删除和移动操作。如果当前节点有子节点,按照这样的做法继续下去,至于何时会插入、合适删除和移动,需要一步步的判断,这里不深究。这样一直循环遍历,最后整个算法的时间复杂度达到了O(n^3),如果这样的算法移植到浏览器的DOM处理上,本来就很慢的DOM操作加上大量的DOM元素和n^3的复杂度,会对性能产生巨大的影响。
React针对diff算法,做了大胆的假设策略,而这些策略在实际项目开发中,也确实如预期一样。
tree diff——DOM节点跨层级操作少,基本可以忽略不计。
这一个策略就可以使diff算法的时间复杂度从n^3下降到n。当diff忽略了跨层级移动节点的时候,就可只比较同一级的元素了,一旦节点发生变化也就是原节点不再的时候,不会再循环遍历它的子节点了,而是会删除整个子节点树。这样就可以对整个DOM节点数只遍历一次就能完成整个过程,时间复杂度也就降到了O(n)。(以上是我自己的理解,说的比较通俗,网上一大堆copy的解释文章写的对新人并不友好)当然也可能会出现跨层级的DOM操作,这时diff的做法就是当作原节点消失的方法,就是删除节点然后添加新的节点,这样可能会引起较大部分的DOM重新渲染,影响性能。所以React官方不建议进行跨DOM的节点操作。
component diff——拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。(简单来说就是:如果不是同一个组件,基本上不会拥有相同的DOM结构)
建立在这一假设上,diff可以在对同一级节点比较时直接比较节点类型。如果属于同一类型就安装传统的方法继续往下比较;如果不是同一类型,就判定当前组件为dirty component,从而替换整个组件下的所有子节点。对于相同类型的组件,可能存在Virtual DOM没有变化情况,如果能确切的知道哪些节点变化了可以节约diff的时间,所以React提供了shouldComponentUpdate()接口给用户,可以让coders手动判断组件是否需要进行diff。
element diff——处于同一层级的节点,可以通过唯一的ID进行区分。
这里的ID一般指的是用户设定的key值。在使用for、map等操作时,React经常会提醒warning报告说需要提供key值,而且ID(key)必须是唯一的,不能有相同的ID值,经常使用index、props.id等作为ID(key)值。在没有使用ID时,如果变化后的DOM只是在同一级进行了move操作,也就说没有数据变化,只有顺序变化,对于diff来说会像之前一样判断节点是否存在,如果消失则删除原节点然后添加新节点,对于这一点来说,diff会变得很不友好,原本Vertival DOM只是做了同级的移动操作,而diff却做了删除添加等操作。而当添加了ID之后,diff会判断相同的ID的节点是否存在,如果存在就保留,如果不存在就删除,如果有新ID的节点会添加进DOM树中。这样就减少了很多删除添加节点的时间,提供了diff算法的性能。所以针对这一点,React官方会在用户进行较多相同子节点创建的时候要求添加key值,这会极大的提升diff算法的性能。
以上所描述的是diff算法的思想,具体的实现是很复杂的。看源码看的满脸懵,主要是不清楚很多函数的作用。我们了解了diff算法之后,就可以跟着它的节奏去编写代码,尽量按着能提高性能方向去实现代码,不要让diff为难。