虚拟Dom和Diff算法

171 阅读4分钟

虚拟 Dom(vdom)

在描述虚拟Dom之前,我们先了解一下原生的DOM。 大致的流程:

  • 构建Dom树;通过HTML Parser解析处理HTML为Dom树。
  • 构建CSSOM树;通过CSS Parser解析处理CSS为CSSOM树。
  • 构建Render树;将Dom树和CSSOM树关联一起构建出Render树。
  • 布局/回流(Layout/Reflow)Render树;浏览器第一次确定节点的位置以及大小叫布局,如果后续节点位置以及大小发生变化,这一步触发布局调整,也就是回流。
  • 绘制/重绘(Paint/Repaint)render树;将元素的每个可视部分绘制到屏幕上,包括文本、颜色、边框、阴影和替换的元素(如按钮和图像)。如果文本、颜色、边框、阴影等这些元素发生变化时,会触发重绘(Repaint)。

可以看出操作原生DOM非常消耗性能,开销很大。而React把真实原生DOM转换成了 JavaScript 对象。这就是虚拟 Dom(Virtual Dom)

Virtual DOM是对DOM的抽象,本质上是JavaScript对象,这个对象就是更加轻量级的对DOM的描述,提高重绘性能。

原生 html 元素代码:

<div class="title">
  <span>Hello World</span>
  <ul>
    <li>A</li>
    <li>B</li>
  </ul>
</div>

在 React 存储为这样的 JS 对象:

const VitrualDom = {
  // 原生元素标签,其 type 为标签名。而如果是函数组件或 class 组件,
  // 其 type 就是对应的 class 或者function 对象
  type: "div",
  props: { class: "title" },
  children: [
    { type: "span", children: "Hello World" },
    {
      type: "ul",
      children: [
        { type: "li", children: "A" },
        { type: "li", children: "B" },
      ],
    },
  ],
};

Diff算法

React 需要同时维护两棵虚拟 DOM 树:一棵表示当前的 DOM 结构,另一棵在 React 状态变更将要重新渲染时生成。React 通过比较这两棵树的差异,决定是否需要修改 DOM 结构,以及如何修改。这种算法称作 Diff 算法。

每次数据更新后,重新计算vdom,并和上一次生成的vdom进行对比,对发生变化的部分作批量更新。React生命周期 componentShouldUpdate 正是让用来开发者手动控制减少数据变化后不必要的虚拟 dom 对比,提升性能和渲染效率。

React 的 diff 算法采用了三种策略,分别是:

  1. 深度优先比较(tree diff):React 会逐层遍历新旧虚拟 DOM 树的节点,并进行比较。当遍历到某个节点时,React 会首先比较节点的类型(例如元素节点、文本节点等),如果类型不同,则直接替换整个节点及其子树。如果类型相同,则继续比较节点的属性和子节点。

  2. 同级比较(component diff):在进行深度优先比较时,React 会比较新旧虚拟 DOM 树中同级别的节点。如果节点的 key 和类型都相同,则说明节点是相同的,React 会保留该节点并继续递归比较其属性和子节点。如果节点的 key 或类型不同,则说明节点发生了变化,React 会替换整个节点及其子树。

  3. 列表节点比较(element diff):当比较列表节点时,React 会使用一种称为 "key" 的特殊属性来标识节点的唯一性。React 通过比较新旧列表中的节点的 key,来判断节点的变化情况。如果新旧列表中的节点 key 相同,则认为节点是相同的,React 会保留该节点并继续递归比较其属性和子节点。如果节点 key 不同,则说明节点发生了变化,React 会替换整个节点及其子树。

    1. key的机制决定了在遍历列表中的key必须是唯一的,若涉及到节点顺序的动态调整,不可以使用index来作为key。不论哪种情况,都不建议使用index来作为key。
    2. key机制的缺点:在节点顺序调整的操作中,若只有最后一个节点变化,移动到第一个节点前面;也会从前往后移动所有节点。所以尽量在开发中减少此类操作。

这些策略使得 React 在进行虚拟 DOM 比较时能够尽可能地减少对真实 DOM 的操作,从而提高性能和效率。通过只更新发生变化的部分,React 可以最小化对真实 DOM 的操作次数,从而实现高效的组件更新和渲染。