编写使用一些可重复使用的UI组件的JS框架(如React
),通过Provider
链接全局store
到这些组件(例如Redux
)已成为常用的开发任务,几乎每一个Web开发人员都这样做。但是接下来,我想让我们更深入地研究,以便对react
有更好的了解,并才了解diff
算法。即使我过去多次听说过这个词,但从未深入了解过。今天,我们将一起了解react
背后的核心概念,例如diff和虚拟dom。
虚拟Dom
是类似于真实DOM的javascript对象。在通过setState,dispatcher
触发的每个变化上react
都会根据这些更改从头开始创建新的虚拟Dom
。React
可以在1秒内产生多达200000棵树。实际上,它可以更快,更有效地生产树。新的虚拟树的创建遵循宽度优先搜索(BFS)策略,因此如果父节点上也发生了更改,则可以通过父节点调整任何节点的更改。如果更改是在根节点本身(树的第一个根)处完成的,则react将废弃完整的树并从头开始创建新树,并再次触发为子组件重新渲染。
您可能想知道为什么我们不降低渲染性能,即使它每次都从头开始创建虚拟树(如果仅在父节点中发生更改)并再次触发子组件的render
方法。因为所有这些都发生在没有触及真实dom的情况下。
但是,如果我们更关心未渲染的子组件的重新渲染,则可以从react
的生命周期中实现已知方法,如下所示:
boolean shouldComponentUpdate(object nextProps, object nextState)
注意:请记住,如果此方法实现不正确,它的性能可能会更差。
在从树的根开始为右节点的左子节点实现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
,开发人员可以确定不同子渲染周期中任何子组件的稳定性。
比较将从树的根节点开始:
- 如果根节点具有两种不同的组件,则react将卸载完整的旧树,并如前所述从头开始创建新树。从此以后,渲染的根组件实例将开始调用诸如
componentWillUnmount
之类的卸载循环方法,而新树中根节点的实例将启动诸如componentWillMount
之类的装载循环方法的调用。 - 如果根节点的类型相同,并且属性有所更改,那么react将仅使用现有DOM中的新更改值来更新那些属性。
在上一个截图中,react将仅更新className
的值,并将节点保留在树中。从此以后,在更新之后,组件实例将以其他更新周期方法(例如componentWillReceiveProps
和componentWillUpdate
被调用。
对于另一个节点(即子节点),此过程递归地继续。请记住,如果以上两个假设中的任何一个都不满足,那么它将对应用程序性能产生影响。
网友评论