React深入理解虚拟dom和diff算法
Posted web前端日记
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了React深入理解虚拟dom和diff算法相关的知识,希望对你有一定的参考价值。
本篇文章同步发表在我的个人博客中:https://zhengyq.club
写在前面
在React
中,Virtual Dom
和diff
的结合大大提高了渲染效率。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 UI
中DOM
节点跨层级的移动操作特别少,可以忽略不计。拥有相同类的两个组件将会生成相似的树形结构,拥有不同类的两个组件将会生成不同的树形结构。
对于同一层级的一组子节点,它们可以通过唯一
id
进行区分。
基于以上三个策略,React
分别对tree diff
、component diff
以及element diff
进行了算法优化。
tree diff
基于第一个策略,react
只会对同一层次的节点进行比较,如下图中,只对颜色相同框内的DOM
节点进行比较,当发现节点不存在时,就会删除整个节点及其子节点,不会再进行比较,这样就只需要遍历一次,就能完成对整个DOM
树的比较
如果出现了DOM
节点的跨层级的移动操作,React diff
会怎样呢?
React
只会简单的考虑同层级节点的位置变换,对于不同层级的节点,只有创建和删除操作。如果A节点整个被移动到D节点下,根节点发现子节点中A不见了,就会销毁A;然后D 发现自己多了一个子节点,就会创建新的子节点(包含其中属于自己的子节点)作为其子节点。react diff
就会按照这样的次序执行:craete a -> create b -> create c -> delete a
。这种跨层级的节点移动,并不会出现移动的情况,而是会有创建、删除这些操作。这种操作会影响到React的性能,所以React官方也并不建议进行这种操作。在开发组件时,保持稳定的dom结构会有助于性能的提升
component diff
React
对于组件间的比较采取的策略也是简洁高效
如果是同一类型的组件,按照原策略继续比较虚拟dom树
如果不是,则将该组件判断为
dirty component
,从而替换整个组件下的所有子节点对于同一类型的组件,有可能其
Virtual DOM
没有任何变化,如果能够确切的知道这点那可以节省大量的diff
运算的时间,因此React
允许用户通过shouldComponentUpdate()
判断该组件是否需要进行diff
举个例子来说,当下图中componentD
改变为componentG
时,即使这两个compoent
结构很相似,但是react
会判断D和G并不是同类型组件,也就不会比较二者的结构了,而是直接删除了d,重新创建G及其子节点,这个时候会影响react的性能
element diff
当节点处于同一层级时,React diff
提供了三种节点操作:插入、移动和删除
插入:新的
component
类型不在老集合里 -> 全新的节点,需要对新节点执行插入操作移动:在老集合里有新
component
类型,且element
是可更新的类型,generateComponentChildren
已调用receiveComponent
,这种情况下prevChild=nextChild
,就需要做移动操作,可以复用以前的dom
节点删除:老的
component
类型,在新集合中也有,但对应的element
不同则不能直接复用和更新,需要执行删除操作,或者老component
不在新集合里,也需要执行删除操作
举个
以上是关于React深入理解虚拟dom和diff算法的主要内容,如果未能解决你的问题,请参考以下文章