深入理解React虚拟DOM

4,563 阅读3分钟

一、什么是虚拟DOM

虚拟DOM可以看做一棵模拟了DOM树的JavaScript对象树。比如:

var element = {
    element: 'ul',
    props: {
        id:"ulist"
    },
    children: [
    { element: 'li', props: { id:"first" }, children: ['这是第一个List元素'] },
    { element: 'li', props: { id:"second" }, children: ['这是第二个List元素'] }
    ]
}

二、为什么使用虚拟DOM

在传统的 Web 应用中,我们往往会把数据的变化实时地更新到用户界面中,于是每次数据的微小变动都会引起 DOM 树的重新渲染。

虚拟DOM的目的是将所有操作累加起来,统计计算出所有的变化后,统一更新一次DOM。

01.png

三、虚拟DOM的原理

当Node节点的更新,虚拟DOM会比较两棵DOM树的区别,保证最小化的DOM操作,使得执行效率得到保证。

计算两棵树的常规算法是O(n^3)级别,所以需要优化深度遍历的算法。React diff算法的时间复杂度为O(n)。

React diff 算法

02.png

React 分别对 tree diff、component diff 以及 element diff 进行算法优化。

1、tree diff

03.jpg

DOM 节点跨层级的移动操作少到可以忽略不计,针对这一现象,React 通过 updateDepth 对 Virtual DOM 树进行层级控制,只会对相同颜色方框内的 DOM 节点进行比较,即同一个父节点下的所有子节点。当发现节点已经不存在,则该节点及其子节点会被完全删除掉,不会用于进一步的比较。这样只需要对树进行一次遍历,便能完成整个 DOM 树的比较。

2、component diff

  • 如果是同一类型的组件,按照原策略继续比较 virtual DOM tree。

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

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

04.png

当 component D 改变为 component G 时,即使这两个 component 结构相似,一旦 React 判断 D 和 G 是不同类型的组件,就不会比较二者的结构,而是直接删除 component D,重新创建 component G 以及其子节点。

3、element diff

节点处于同一层级时,React diff 提供了三种节点操作,分别为:INSERT_MARKUP(插入)、MOVE_EXISTING(移动)和 REMOVE_NODE(删除)。

05.png

新老集合所包含的节点,如下图所示,新老集合进行 diff 差异化对比,通过 key 发现新老集合中的节点都是相同的节点,因此无需进行节点删除和创建,只需要将老集合中节点的位置进行移动,更新为新集合中节点的位置,此时 React 给出的 diff 结果为:B、D 不做任何操作,A、C 进行移动操作,即可。

总结

  • React 通过制定大胆的 diff 策略,将 O(n3) 复杂度的问题转换成 O(n) 复杂度的问题;
  • React 通过分层求异的策略,对 tree diff 进行算法优化;
  • React 通过相同类生成相似树形结构,不同类生成不同树形结构的策略,对 component diff 进行算法优化;
  • React 通过设置唯一 key的策略,对 element diff 进行算法优化