React 原理篇 - 深入理解虚拟 DOM

发布于:2025-09-14 ⋅ 阅读:(22) ⋅ 点赞:(0)

一、什么是虚拟 DOM?

在前端开发中,“虚拟 DOM” 是一个高频出现的术语,尤其在 React 生态中被广泛讨论。但很多开发者对它的理解往往停留在 “JS 对象” 这个表层认知上。

实际上,虚拟 DOM 是一种编程概念—— 在这个概念里,UI 以一种理想化的、“虚拟的” 表现形式被保存于内存中。它本质上是对真实 DOM 的一种描述,而不是具体的实现。

React 官方文档对虚拟 DOM 的定义是:“一种编程概念,UI 以一种理想化的,或者说 ’ 虚拟的 ’ 表现形式被保存于内存中”。这意味着,只要能描述真实 DOM 的层次结构,无论采用何种形式(JSON、XML 等),都可以称为虚拟 DOM。而 React 选择了JS 对象作为这种思想的具体实现。

二、为什么需要虚拟 DOM?

在深入了解虚拟 DOM 的工作原理前,我们先思考一个问题:为什么需要虚拟 DOM?它解决了什么痛点?

2.1 性能优化:减少 DOM 操作成本

浏览器操作 DOM 的成本非常高,主要原因有两点:

  • DOM 对象包含大量属性和方法(远多于普通 JS 对象)
  • DOM 操作会触发浏览器的重排(Reflow)和重绘(Repaint)

相比之下,JS 层面的计算成本要低得多。通过先在 JS 层面对虚拟 DOM 进行计算和比对,再映射到真实 DOM,可以大幅减少不必要的 DOM 操作。

虚拟 DOM 的优势在更新阶段尤为明显 —— 它避免了全量销毁重建 DOM 的昂贵操作,只做必要的修改。

2.2 跨平台渲染能力

虚拟 DOM 最大的价值之一是提供了平台无关性。它将 UI 描述与具体渲染逻辑分离,使得同一套代码可以在不同平台渲染:

  • 浏览器环境:通过 ReactDOM 渲染为 DOM
  • 移动设备:通过 React Native 渲染为原生组件
  • 服务端:通过 ReactDOMServer 渲染为字符串
  • 测试环境:直接渲染为 JS 对象进行断言

这种抽象能力完美契合了 “一次编写,多端运行” 的现代开发需求。

三、React 中的虚拟 DOM 实现

在 React 中,虚拟 DOM 以 “React 元素” 的形式存在。让我们从 JSX 开始,一步步揭开它的面纱。

3.1 JSX 与 createElement 的关系

我们编写的 JSX 代码:

<h1 className="title" id="header">
  Hello, Virtual DOM!
  <span>React</span>
</h1>

会被 Babel 编译为 createElement 方法的调用:

React.createElement(
  'h1',
  { className: 'title', id: 'header' },
  'Hello, Virtual DOM!',
  React.createElement('span', null, 'React')
)

这个方法最终会返回一个描述 DOM 结构的 JS 对象 —— 这就是 React 中的虚拟 DOM:

{
  $$typeof: Symbol(react.element),
  type: 'h1',
  key: null,
  ref: null,
  props: {
    className: 'title',
    id: 'header',
    children: [
      'Hello, Virtual DOM!',
      {
        $$typeof: Symbol(react.element),
        type: 'span',
        // ... 其他属性
      }
    ]
  },
  _owner: null
}

3.2 createElement 核心逻辑解析

React 的 createElement 函数主要做了四件事:

  1. 处理属性(过滤保留字、提取 key 和 ref)
  2. 处理子元素(支持多个子节点,转为数组)
  3. 合并默认属性(defaultProps)
  4. 创建并返回 React 元素对象

简化版实现如下:

function createElement(type, config, children) {
  const props = {};
  let key = null;
  let ref = null;

  // 处理配置属性
  if (config) {
    key = config.key || null;
    ref = config.ref || null;
    // 复制非保留字属性到 props
    for (const prop in config) {
      if (config.hasOwnProperty(prop) && !RESERVED_PROPS.hasOwnProperty(prop)) {
        props[prop] = config[prop];
      }
    }
  }

  // 处理子元素
  const childrenLength = arguments.length - 2;
  if (childrenLength === 1) {
    props.children = children;
  } else if (childrenLength > 1) {
    props.children = Array.from(arguments).slice(2);
  }

  // 合并默认属性
  if (type && type.defaultProps) {
    for (const prop in type.defaultProps) {
      if (props[prop] === undefined) {
        props[prop] = type.defaultProps[prop];
      }
    }
  }

  return {
    $$typeof: REACT_ELEMENT_TYPE,
    type,
    key,
    ref,
    props,
    _owner: null
  };
}

这个对象包含了渲染真实 DOM 所需的全部信息:元素类型(type)、属性(props)、子元素(children)等。

四、虚拟 DOM 的工作流程

虚拟 DOM 不是孤立存在的,它是 React 渲染流程的核心环节。完整流程包括:

4.1 初始渲染

  • 从 JSX 生成虚拟 DOM
  • 将虚拟 DOM 转换为真实 DOM 并插入页面

4.2 状态更新

  • 状态变化触发重新渲染,生成新的虚拟 DOM
  • 通过 Diff 算法对比新旧虚拟 DOM,找出差异
  • 只将差异部分应用到真实 DOM(Patch 过程)

4.3 Diff 算法:高效比对的核心

React 的 Diff 算法是虚拟 DOM 实现高性能的关键,它基于三个假设:

  • 不同类型的元素会产生不同的树
  • 可以通过 key 属性标识同一层级的子元素,保持稳定性
  • 只进行同层级比对(时间复杂度 O (n))

这种策略大幅简化了比对过程,同时保证了大多数场景下的高效性。

五、虚拟 DOM 的局限性

尽管虚拟 DOM 带来了诸多好处,但也存在一定问题:

  1. 初始渲染开销:首次渲染时,虚拟 DOM 因为多了一层 JS 计算,可能比直接操作 DOM 稍慢
  2. 过度优化:对于简单场景,手动优化的 DOM 操作可能比虚拟 DOM 更高效
  3. 内存占用:大量虚拟 DOM 对象可能占用较多内存

React 团队也在不断优化这些问题,例如通过 Fiber 架构实现增量渲染,进一步提升性能。

六、总结

虚拟 DOM 是 React 架构的基石,它通过 “描述式编程” 的思想,让开发者专注于 UI 应该是什么样子,而不是如何操作 DOM。

核心要点:

  • 虚拟 DOM 是一种思想,JS 对象是 React 中的实现方式
  • 主要优势是减少 DOM 操作和提供跨平台能力
  • 工作流程包括:创建虚拟 DOM → 比对差异 → 应用差异
  • React 通过 createElement 方法将 JSX 转换为虚拟 DOM 对象