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

一、什么是虚拟 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;// 复制非保留字属性到 propsfor (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 对象

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/web/98025.shtml
繁体地址,请注明出处:http://hk.pswp.cn/web/98025.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

对汇编的初理解

此处是一个简单的函数&#xff0c;里面将调用了一个函数add&#xff08;&#xff09;函数这里是函数的原型这里是调用lcd函数产生的汇编语言&#xff0c;翻译过来就是r11&#xff0c;r0cnt(r4cnt,前文有提及)&#xff0c;然后调用add函数&#xff0c;此处BL是指会回到指令的下一…

《Python 自动化实战:从零构建一个文件同步工具》

《Python 自动化实战:从零构建一个文件同步工具》 一、开篇引入:为什么我们需要文件同步? 你是否有过这样的困扰: 公司电脑和家里电脑上都有工作项目,每次更新都要手动复制? U 盘频繁传输文件,不仅麻烦还容易出错? 项目文件夹动辄几 G,每次同步都耗时长、效率低? 在…

工业相机与镜头的靶面尺寸详解:选型避坑指南

在机器视觉系统中&#xff0c;相机与镜头的靶面尺寸匹配是一个非常关键却又经常被忽略的细节。选错了&#xff0c;不但影响图像质量&#xff0c;还可能导致画面“黑角”、视野不符、镜头浪费等问题。 今天我们就用通俗易懂的方式&#xff0c;聊一聊相机与镜头靶面尺寸的那些事儿…

使用 Go 和 go-commons 实现内存指标采集并对接 Prometheus

文章目录一、准备工作二、编写内存采集代码三、运行 Exporter四、接入 Prometheus五、可扩展思路总结在运维和监控领域&#xff0c;资源指标采集 是必不可少的一环。CPU、内存、磁盘、网络这些系统资源&#xff0c;需要实时采集并上报到监控系统中。 本文以 内存指标采集 为例&…

webrtc弱网-IntervalBudget类源码分析与算法原理

一、核心功能 IntervalBudget 类用于基于时间窗口的带宽预算管理。它根据设定的目标比特率&#xff08;kbps&#xff09;和一个固定时间窗口&#xff08;500ms&#xff09;&#xff0c;计算在该时间窗口内可用的字节数&#xff08;即“预算”&#xff09;&#xff0c;并支持预…

深度学习基本模块:RNN 循环神经网络

循环神经网络&#xff08;RNN&#xff09;是一种专门用于处理序列数据的神经网络架构。与处理空间数据的卷积神经网络&#xff08;Conv2D&#xff09;不同&#xff0c;RNN通过引入循环连接使网络具有"记忆"能力&#xff0c;能够利用之前的信息来影响当前的输出&#…

React18学习笔记(二) React的状态管理工具--Redux,案例--移动端外卖平台

文章目录一.Redux的基础用法1.示例:普通网页中的Redux计步器2.Redux管理数据的流程3.配套工具和环境准备3.1.配套工具3.2.环境准备4.示例:React项目中的Redux计步器思路步骤step1:创建子模块step2:导入子模块step3:注入store实例step4:React组件内使用store中的数据step5:在组件…

34.Socket编程(UDP)(上)

点分十进制字符串IP 转 32位网络序列IP 分析&#xff1a;1&#xff09;IP转成4字节 2&#xff09;4字节转成网络序列 思路&#xff1a; "192.168.1.1" 进行字符串划分&#xff0c;以 "." 为分割符&#xff0c;分割出"192"&#xff0c;&qu…

Redis的持久化工具包—RDB AOF

文章目录 前言 一、RDB 持久化&#xff08;快照持久化&#xff09; 1. 定义 2. RDB 触发机制 &#xff08;1&#xff09;手动触发 &#xff08;2&#xff09;自动触发 3. RDB 持久化流程 4. RDB 核心配置 5. RDB 优缺点 二、AOF 持久化&#xff08;日志持久化&#xff09; 1. 定…

【Web安全】XXL-JOB框架SRC高频漏洞分析总结

文章目录前言一、核心漏洞分类与技术细节二、漏洞关联利用与攻击路径三、版本演进与修复策略四、安全运维建议五、典型漏洞复现环境搭建六、总结前言 XXL-JOB是国内主流的开源分布式任务调度框架&#xff0c;由徐雪里开发维护&#xff0c;以轻量易用、高可用、适配分布式场景等…

Capacitor 打包后接口访问不到的排查经历

我最近在用 Quasar Capacitor 6 做一个 Android App&#xff0c;前端用的是 Vue3 Quasar&#xff0c;打包交给 Capacitor 去跑在手机的 WebView 里&#xff0c;后端是 FastAPI 提供接口。开发模式下一切顺利&#xff0c;浏览器里访问接口没有任何问题&#xff0c;我甚至觉得打…

【正点原子】Linux应用编程入门~概念及环境介绍

应用编程概念 应用编程&#xff08;也可称为系统编程&#xff09;与驱动编程、裸机编程有何不同&#xff1f;系统调用&#xff1b;何为库函数&#xff1b;应用程序的 main()函数&#xff1b;应用程序开发环境的介绍&#xff1b;系统调用 定义系统调用&#xff08;system call&a…

一、HTML 完全指南:从零开始构建网页

文章目录前言一、 HTML 结构认识 HTML 标签HTML 文件基本结构标签层次结构快速生成代码框架二、 HTML 常见标签详解2.1 注释标签2.2 标题标签 (h1 - h6)2.3 段落标签 (p)2.4 换行标签 (br)2.5 格式化标签2.6 图片标签 (img)2.7 超链接标签 (a)2.8 表格标签基本使用合并单元格2.…

基于POI-TL实现动态Word模板的数据填充:【散点图】特殊处理方案

基于POI-TL实现动态Word模板的数据填充:散点图特殊处理方案 在使用POI-TL进行Word模板动态数据填充时,图表生成是一个常见需求。最近在项目中使用POI-TL处理散点图时遇到了一个特殊问题,经过研究后找到了解决方案,特此记录分享。 问题背景 POI-TL作为一款优秀的Java Wor…

使用node-Express框架写一个学校宿舍管理系统练习项目-前后端分离

今天继续分享一个新的练习项目&#xff0c;是使用node做为后端语言&#xff0c;来写的一个前后端分离项目&#xff1a;学校宿舍管理系统。我们如果想掌握一门编程语言&#xff0c;就是需要大量的练习。所以当我们学习到了一些知识&#xff0c;自己想一下 可以拿学到的知识&…

Kafka 运维实战基本操作含命令与最佳实践

1. 基础概览与工具入口 Kafka 发行包的所有 CLI 工具均在 bin/ 目录下。任何工具不带参数运行都会显示所有可用选项。本文命令默认&#xff1a;--bootstrap-server localhost:9092&#xff1b;生产请替换为你的控制面或内网 VIP。 2. 主题管理&#xff08;创建 / 修改 / 删除 /…

贪心算法应用:航班起降问题详解

Java中的贪心算法应用&#xff1a;航班起降问题详解 贪心算法是一种在每一步选择中都采取当前状态下最优的选择&#xff0c;从而希望导致全局最优解的算法策略。在航班起降问题中&#xff0c;贪心算法可以有效地解决机场跑道调度问题&#xff0c;即如何安排航班的起降顺序以最大…

uniapp scroll-view 设置scrollTop无效

当我们使用 scroll-view的scroll-top的时候 默认想让它回到顶部&#xff0c;当我们设置值为0的时候会不生效&#xff0c;在实际运用过程中&#xff0c;发现设置了scroll-top无效&#xff0c;滚动条位置并没有发生变化&#xff0c;是因为微信小程序的官方框架处于性能考虑&#…

网络与通信

1.TCP协议与UDP协议TCP&#xff08;Transmission Control Protocol&#xff0c;传输控制协议&#xff09;和 UDP&#xff08;User Datagram Protocol&#xff0c;用户数据报协议&#xff09;是 TCP/IP 协议族中两种核心的传输层协议&#xff0c;它们在数据传输方式、可靠性、适…

Node.js中package.json详解

1. name&#xff08;名称&#xff09; 如果你计划发布你的包&#xff0c;package.json 中最重要的字段是 name 和 version&#xff0c;因为它们是必需的。name 和 version 共同组成一个假定完全唯一的标识符。包的更改应伴随版本号的更新。如果你不打算发布包&#xff0c;那么…