前言:
而今我们对“diff算法过程”大概比较讲究,咱们都需要学习一些“diff算法过程”的相关文章。那么小编在网上搜集了一些对于“diff算法过程””的相关资讯,希望咱们能喜欢,兄弟们一起来学习一下吧!React采用声明式的API描述UI结构,每次组件的状态或属性更新,组件的render方法都会返回一个新的虚拟DOM对象,用来表述新的UI结构。如果每次render都直接使用新的虚拟DOM来生成真实DOM结构,那么会带来大量对真实DOM的操作,影响程序执行效率。事实上,React会通过比较两次虚拟DOM结构的变化找出差异部分,更新到真实DOM上,从而减少最终要在真实DOM上执行的操作,提高程序执行效率。这一过程就是React的调和过程(Reconciliation),其中的关键是比较两个树形结构的Diff算法。
注意在Diff算法中,比较的两方是新的虚拟DOM和旧的虚拟DOM,而不是虚拟DOM和真实DOM,只不过Diff的结果会更新到真实DOM上。正常情况下,比较两个树形结构差异的算法的时间复杂度是O(N^3),这个效率显然是无法接受的。React通过总结DOM的实际使用场景提出了两个在绝大多数实践场景下都成立的假设,基于这两个假设,React实现了在O(N)时间复杂度内完成两棵虚拟DOM树的比较。这两个假设是:(1)如果两个元素的类型不同,那么它们将生成两棵不同的树。(2)为列表中的元素设置key属性,用key标识对应的元素在多次render过程中是否发生变化。下面介绍在不同情况下,React具体是如何比较两棵树的差异的。React比较两棵树是从树的根节点开始比较的,根节点的类型不同,React执行的操作也不同。
1.当根节点是不同类型时从div变成p、从ComponentA变成ComponentB,或者从ComponentA变成div,这些都是节点类型发生变化的情况。根节点类型的变化是一个很大的变化,React会认为新的树和旧的树完全不同,不会再继续比较其他属性和子节点,而是把整棵树拆掉重建(包括虚拟DOM树和真实DOM树)。这里需要注意,虚拟DOM的节点类型分为两类:一类是DOM元素类型,比如div、p等;一类是React组件类型,比如自定义的React组件。在旧的虚拟DOM树被拆除的过程中,旧的DOM元素类型的节点会被销毁,旧的React组件实例的componentWillUnmount会被调用;在重建的过程中,新的DOM元素会被插入DOM树中,新的组件实例的componentWillMount和componentDidMount方法会被调用。重建后的新的虚拟DOM树又会被整体更新到真实DOM树中。这种情况下,需要大量DOM操作,更新效率最低。
2.当根节点是相同的DOM元素类型时如果两个根节点是相同类型的DOM元素,React会保留根节点,而比较根节点的属性,然后只更新那些变化了的属性。例如:[插图]React比较这两个元素,发现只有className属性发生了变化,然后只更新虚拟DOM树和真实DOM树中对应节点的这一属性。
3.当根节点是相同的组件类型时如果两个根节点是相同类型的组件,对应的组件实例不会被销毁,只是会执行更新操作,同步变化的属性到虚拟DOM树上,这一过程组件实例的componentWillReceiveProps()和componentWillUpdate()会被调用。注意,对于组件类型的节点,React是无法直接知道如何更新真实DOM树的,需要在组件更新并且render方法执行完成后,根据render返回的虚拟DOM结构决定如何更新真实DOM树。比较完根节点后,React会以同样的原则继续递归比较子节点,每一个子节点相对于其层级以下的节点来说又是一个根节点。如此递归比较,直到比较完两棵树上的所有节点,计算得到最终的差异,更新到DOM树中。当一个节点有多个子节点时,默认情况下,React只会按照顺序逐一比较两棵树上对应的子节点。例如,比较下面的两个节点,两棵树上的<li>first</li>和<li>second</li>子节点会分别被匹配,最终只会插入一个新的节点<li>third</li>。
但如果在子节点的开始位置新增一个节点,情况就会变得截然不同。例如下面的例子,<li>third<li>插入子节点的第一个位置,React会把第一棵树的<li>first</li>和第二棵树的<li>third</li>进行比较,把第一棵树的<li>second</li>和第二棵树的<li>first</li>进行比较,最后发现新增了一个<li>second</li>节点。这种比较方式会导致每一个节点都被修改。[插图]为了解决这种低效的更新方式,React提供了一个key属性。在2.4节已经介绍过,当渲染列表元素时,需要为每一个元素定义一个key。这个key就是为了帮助React提高Diff算法的效率。当一组子节点定义了key,React会根据key来匹配子节点,在每次渲染之后,只要子节点的key值没有变化,React就认为这是同一个节点。例如,为前面的例子定义key:
定义key之后,React就能判断出<li key="third">third</li>这个节点是新增节点,<likey="first">first</li>和<li key="second">second</li>两个节点并没有发生改变,只是位置发生了变化而已。如此一来,React只需要执行一次插入新节点的操作。这里同时揭露了另一个问题,尽量不要使用元素在列表中的索引值作为key,因为列表中的元素顺序一旦发生改变,就可能导致大量的key失效,进而引起大量的修改操作。例如,下面的写法应该尽量避免:[插图]最后,需要提醒读者,本章介绍的Diff算法是React当前采用的实现方式,React会不断改进Diff算法,以提高DOM比较和更新的效率。
标签: #diff算法过程