React深入理解虚拟dom和diff算法

Posted web前端日记

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React深入理解虚拟dom和diff算法相关的知识,希望对你有一定的参考价值。

本篇文章同步发表在我的个人博客中:https://zhengyq.club

写在前面

React中,Virtual Domdiff的结合大大提高了渲染效率。diff算法由最初的O(n^3)复杂度变为了现在的O(n),那么在这其中都做了哪些事情,本篇文章为你揭晓答案~

虚拟dom和diff

虚拟dom是什么?

Virtual DOM是一种编程概念。在这个概念里,UI以一种理想化的,或者说“虚拟的”表现形式被保存于内存中。在React中,render执行的结果得到的并不是真正的DOM节点,结果仅仅是轻量级的javascript对象。像这样:

<div class="box">
    <span>111</span>
</div>

上面这段代码会转换为这样的虚拟DOM结构

{
    tag: "div",
    props: {
        class: "box"
    },
    children: [
        "hello world!"
    ]
}

diff算法又是什么?

diff算法,会对比新老虚拟DOM,记录下他们之间的变化,然后将变化的部分更新到视图上。其实之前的diff算法,是通过循环递归每个节点,然后进行对比,复杂程度为O(n^3)n是树中节点的总数,这样性能是非常差的。

dom-diff的原理

diff算法会比较前后虚拟DOM,从而得到patches(补丁),然后与老Virtual DOM进行对比,将其应用在需要更新的地方,得到新的Virtual DOM,在网上有一张非常直观的图可以帮忙参考

来解释下这张图:现有一个真实的DOM,首先会映射为虚拟DOM,这个时候,我们删除了最后一个p节点和son2的节点,得到了新的一个虚拟DOM,新的vdom会和旧的vdom进行差异对比,得到了pathes对象,之后,对旧的真实dom进行操作,得到了新的DOM

diff的几种策略

  • Web UIDOM节点跨层级的移动操作特别少,可以忽略不计。

  • 拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。

  • 对于同一层级的一组子节点,它们可以通过唯一id进行区分。

基于以上三个策略,React分别对tree diffcomponent diff以及element diff进行了算法优化。

tree diff

基于第一个策略,react只会对同一层次的节点进行比较,如下图中,只对颜色相同框内的DOM节点进行比较,当发现节点不存在时,就会删除整个节点及其子节点,不会再进行比较,这样就只需要遍历一次,就能完成对整个DOM树的比较

【React】深入理解虚拟dom和diff算法

如果出现了DOM节点的跨层级的移动操作,React diff会怎样呢?

React只会简单的考虑同层级节点的位置变换,对于不同层级的节点,只有创建和删除操作。如果A节点整个被移动到D节点下,根节点发现子节点中A不见了,就会销毁A;然后D 发现自己多了一个子节点,就会创建新的子节点(包含其中属于自己的子节点)作为其子节点。react diff就会按照这样的次序执行:craete a -> create b -> create c -> delete a。这种跨层级的节点移动,并不会出现移动的情况,而是会有创建、删除这些操作。这种操作会影响到React的性能,所以React官方也并不建议进行这种操作。在开发组件时,保持稳定的dom结构会有助于性能的提升

【React】深入理解虚拟dom和diff算法

component diff

React对于组件间的比较采取的策略也是简洁高效

  • 如果是同一类型的组件,按照原策略继续比较虚拟dom树

  • 如果不是,则将该组件判断为dirty component,从而替换整个组件下的所有子节点

  • 对于同一类型的组件,有可能其Virtual DOM没有任何变化,如果能够确切的知道这点那可以节省大量的diff运算的时间,因此React允许用户通过shouldComponentUpdate()判断该组件是否需要进行diff

举个例子来说,当下图中componentD改变为componentG时,即使这两个compoent结构很相似,但是react会判断D和G并不是同类型组件,也就不会比较二者的结构了,而是直接删除了d,重新创建G及其子节点,这个时候会影响react的性能

【React】深入理解虚拟dom和diff算法

element diff

当节点处于同一层级时,React diff提供了三种节点操作:插入、移动和删除

  • 插入:新的component类型不在老集合里 -> 全新的节点,需要对新节点执行插入操作

  • 移动:在老集合里有新component类型,且element是可更新的类型,generateComponentChildren已调用receiveComponent,这种情况下prevChild=nextChild,就需要做移动操作,可以复用以前的dom节点

  • 删除:老的component类型,在新集合中也有,但对应的element不同则不能直接复用和更新,需要执行删除操作,或者老component不在新集合里,也需要执行删除操作

举个

以上是关于React深入理解虚拟dom和diff算法的主要内容,如果未能解决你的问题,请参考以下文章

深入理解react中的虚拟DOMdiff算法

深入理解react中的虚拟DOMdiff算法

深入分析虚拟DOM的渲染过程和特性

React的虚拟DOM与diff算法的理解

关于React中的虚拟DOM与Diff算法

ReactiveNative学习之Diff算法