美文网首页
深入挖掘React Diff算法

深入挖掘React Diff算法

作者: 魂斗驴 | 来源:发表于2021-02-02 07:37 被阅读0次

    编写使用一些可重复使用的UI组件的JS框架(如React),通过Provider链接全局store到这些组件(例如Redux)已成为常用的开发任务,几乎每一个Web开发人员都这样做。但是接下来,我想让我们更深入地研究,以便对react有更好的了解,并才了解diff算法。即使我过去多次听说过这个词,但从未深入了解过。今天,我们将一起了解react背后的核心概念,例如diff和虚拟dom。

    虚拟Dom是类似于真实DOM的javascript对象。在通过setState,dispatcher触发的每个变化上react都会根据这些更改从头开始创建新的虚拟DomReact可以在1秒内产生多达200000棵树。实际上,它可以更快,更有效地生产树。新的虚拟树的创建遵循宽度优先搜索(BFS)策略,因此如果父节点上也发生了更改,则可以通过父节点调整任何节点的更改。如果更改是在根节点本身(树的第一个根)处完成的,则react将废弃完整的树并从头开始创建新树,并再次触发为子组件重新渲染。

    Diff

    您可能想知道为什么我们不降低渲染性能,即使它每次都从头开始创建虚拟树(如果仅在父节点中发生更改)并再次触发子组件的render方法。因为所有这些都发生在没有触及真实dom的情况下。
    但是,如果我们更关心未渲染的子组件的重新渲染,则可以从react的生命周期中实现已知方法,如下所示:

    boolean shouldComponentUpdate(object nextProps, object nextState)
    

    注意:请记住,如果此方法实现不正确,它的性能可能会更差。

    在从树的根开始为右节点的左子节点实现shouldComponentUpdate之后,渲染树,并用虚线边界隔开,如下所示:

    Render tree after shouldComponentUpdate

    如果有任何组件即使父节点被修改也将是相同的(或一致的)。我们可以通过提供“ key ”来指示对保留现有组件实例的反应,以便可以通过卸载将其保存下来,如下图所示:

    Key attribute List of item

    如果我们在(li tag)中列出了要渲染的项目,并且这些列表项可以随时重新排序或重新排列(例如:在两个项目之间添加项目)。因此,在那种情况下,如果我们在li元素中缺少key的值,那么在每个重新渲染周期,react都会卸载所有的item-node并以更新的顺序再次将其重新装载到同一节点。因此,为了避免这种情况,请提供key的的值并保证在项目中是唯一的,并且在整个重新渲染周期中,其值都应相同(因此,避免将数组索引用作键的值)。因为key的值将充当react的标识符,因此它可以帮助在进一步的重新渲染周期中保留这些项。

    一旦创建了新的虚拟树,它就会通过diff与现有的虚拟树进行比较。通过diff算法在树之间进行比较,react尝试确定更新实际DOM所需的最小差异。识别出差异后,react尝试将这些多个更新过程进行一次批量处理,以便可以最大程度地减少重排重绘事件。由于这两个事件(即重排和重绘)是较昂贵的操作,因此是DOM缓慢且耗时的真正原因。

    基本上,所有知名框架(如React,Angular,Vue)都试图通过其实现方式解决类似问题。主要问题是保持UI组件的状态与UI变化同步。最初在这些框架背后有一个神话,他们试图在基于组件的体系结构中实现JS。但是基于组件的实现JS的想法并不是什么新鲜事物,因为过去已经使用vanilla JS.实现了它。但是通过vanilla JS保持同步确实非常痛苦。

    diff算法是框架性能和良好效率背后的真正主要因素。要计算两棵树之间的差异,将采用O(n^3)的复杂度,即,如果存在100个HTML节点,则在最坏的情况下将进行1000000次迭代,这在成本和成本方面都太高了时间。仅对于100个节点,时间复杂度就达到了100万。如果DOM树上的HTML节点数增加,则增长速度将更快,并且复杂度将成倍增加至其他“百万”级(例如数十亿,万亿,四千万等)。为了克服这个问题,反应团队采用了启发式方法,复杂度降为O(n)。他们有两个假设:

    • 不同类型的两个元素将始终产生两个不同的DOM树。
    • 通过提供key,开发人员可以确定不同子渲染周期中任何子组件的稳定性。

    比较将从树的根节点开始:

    1. 如果根节点具有两种不同的组件,则react将卸载完整的旧树,并如前所述从头开始创建新树。从此以后,渲染的根组件实例将开始调用诸如componentWillUnmount之类的卸载循环方法,而新树中根节点的实例将启动诸如componentWillMount之类的装载循环方法的调用。
    2. 如果根节点的类型相同,并且属性有所更改,那么react将仅使用现有DOM中的新更改值来更新那些属性。
    attribute change for node

    在上一个截图中,react将仅更新className的值,并将节点保留在树中。从此以后,在更新之后,组件实例将以其他更新周期方法(例如componentWillReceivePropscomponentWillUpdate被调用。
    对于另一个节点(即子节点),此过程递归地继续。请记住,如果以上两个假设中的任何一个都不满足,那么它将对应用程序性能产生影响。

    参考

    Digging deeper inside the Reconciliation algorithm of react

    相关文章

      网友评论

          本文标题:深入挖掘React Diff算法

          本文链接:https://www.haomeiwen.com/subject/ckastltx.html