手把手教你React(二) - 虚拟dom

640 阅读4分钟

1. 什么是虚拟dom

虚拟dom是React的基础,也是核心概念之一。在上一张中我们介绍了虚拟dom一方面通过和diff算法的结合极大的提升了页面交互的性能,同时它也是跨平台编码的基础,包括了服务端渲染(node)和native编码(React Native)。

虚拟dom其实相当于在js和真实dom之间建立了一个缓存,当我们需要操作dom时,会先通过diff算法计算出虚拟dom变化的最小差异,并只针对最小差异更新真实dom

回到什么是虚拟dom,虚拟dom本质上是一个js对象。举个例子:

<ul id='list'>
  <li class='item'>1</li>
  <li class='item'>2</li>
  <li class='item'>3</li>
</ul>

上面的html转化成虚拟dom如下所示:

const tree = {
  tagName: 'ul',
  props: {      
    id: 'list'
  },
  children: [
    {tagName: 'li', props: {class: 'item'}, children: ['1']},
    {tagName: 'li', props: {class: 'item'}, children: ['2']},
    {tagName: 'li', props: {class: 'item'}, children: ['3']},
  ]
}

2. 为什么需要虚拟dom

虚拟dom带来的好处主要有3点:

1. 提升性能:众所周知操作真实dom慢,虚拟dom快。其具体原因在于当我们操作真实dom的时候会导致浏览器的重绘(repaint)和回流(reflow),当我们操作真实dom时其实浏览器做了以下操作:

  1. 构建dom树

  2. 构建css样式表树

  3. 将dom树和css样式树结合起来,形成待渲染的render树

  4. 确定render树上每个节点的位置、尺寸等信息(reflow)。

  5. 将内容渲染到对应的位置(paint)。

可以看出操作dom的代价是昂贵的,所以我们需要避免频繁操作dom。而虚拟dom的实现使我们可以合并多次dom操作并计算出需要更新的最小dom树并更新,从而提高性能。

2. 一次开发,多端渲染。在node环境中没有dom,但借助于虚拟dom,我们可以模拟真实dom并进行服务端渲染,同时在跨端开发领域,React Native、 Weex等框架也都是基于虚拟dom来实现的。

3. 结合Jsx语法,虚拟dom提供了一种新的开发方式,可以声明式的开发页面,同时使组件化的开发方式得以广泛应用,提升了团队开发的代码的可读性和可维护性。

3. diff算法

提到虚拟dom就不得不提到diff算法,diff算法故名思议就是用来计算两个树的差异的算法,应用在React中用来计算两个虚拟dom树的最小差异,从而更新。然而diff算法的时间复杂度是O(n^3),这在react中显然是不能接受的,所以React对传统的diff算法进行了一些改造。

1. tree diff:只比较同层级的节点。对于整个树的diff,由于用户跨层级操作dom的情况少到基本可以忽略不计。这个优化可以极大的提升diff算法的性能,时间复杂度从O(n^3)降到了O(n)。

2. component diff:对于组件的diff操作核心是两点,第一判断两个组件是不是同一类型的组件,如果不是则替换掉该组件及其子组件,如果是同一类型组件则继续进行tree diff。注意,在这里React提供了一个componentShouldMount的声明周期给到开发者,当两次渲染的组件没有任何变化时可以在该生命周期中返回false,这样则直接渲染原组件内容,而不用在进行递归的diff比较,可以极大的节省性能。

3. element diff:当进行element比较时,React主要有添加、删除和移动三种操作。需要注意的是列表组件的比较,举个例子:

循环比较新老节点,当发现新节点和老节点不一样时,会删除老节点,添加新节点。当我们做上述更改时,做了4次删除和4次新增操作。React发现这是非常低效的,其实元素没有任何变化,只是位置发生了改变。针对这个React提出在渲染列表时给每个节点添加唯一的key值。并将老节点的key值抽到一个Map中,遍历新节点时,判断该节点是否在老节点中存在,如果存在则只进行移动操作。这个优化同样能极大的改善diff的性能。

总结

  1. 虚拟dom是对真实dom的抽象表达,本质是一个普通object对象,相当于在js和真实dom之间搭起的一层缓存。
  2. 虚拟dom的优势在于提升页面渲染性能,一次开发多端渲染。
  3. diff算法搭配虚拟dom可以极大提升页面性能。
  4. 在传统diff算法的基础上React做了几点优化:1. 只比较同层级的节点。 2.  提供componentShouldUpdate钩子函数 3. 添加key属性提高元素渲染性能。