菜单

虚拟 DOM

相关源文件

本文档涵盖了 Vue.js 的虚拟 DOM 实现,这是 Vue 渲染系统的核心部分。虚拟 DOM 在组件状态和实际 DOM 之间提供了一个抽象层,实现了高效的更新和跨平台渲染能力。有关使用虚拟 DOM 的渲染器实现信息,请参阅 渲染系统

概述

虚拟 DOM 是实际 DOM 的轻量级 JavaScript 表示。在 Vue 中,它以 VNode(虚拟节点)对象的树的形式存在,这些对象由组件的渲染函数生成。当应用程序状态发生变化时,Vue 不会直接操作 DOM,而是先更新这个虚拟表示,将其与前一个版本进行比较,然后仅将必要的最小更改应用到真实 DOM。

来源

VNode 结构

VNode(虚拟节点)是 Vue 虚拟 DOM 的基本构建块,它代表 UI 中的一个元素、组件或片段。每个 VNode 都包含创建和更新真实 DOM 节点所需的所有信息。

来源

VNode 接口包含几个关键属性

  • type: 标识节点类型(HTML 元素为字符串,组件为组件对象)
  • props: 包含节点的属性和特性
  • children: 包含子 VNode 或文本
  • el: 指向实际 DOM 节点(挂载前为 null)
  • shapeFlag: 位标志,用于快速识别节点特征
  • patchFlag: 优化标志,指示节点可能需要的更新类型
  • dynamicChildren: 用于跟踪块优化的节点的可选数组

VNode 类型和特殊标志

Vue 使用几种特殊的 VNode 类型和标志来优化渲染

来源

特殊 VNode 类型包括

  • Fragment: 聚合多个节点而不添加额外的 DOM 元素
  • Text: 表示文本内容
  • Comment: 表示 HTML 注释
  • Static: 表示永不变动的静态内容
  • Teleport: 用于将子节点渲染到 DOM 中其他位置的特殊组件
  • Suspense: 用于处理异步依赖的组件

VNode 创建

createVNode 函数是创建虚拟节点的关键 API。它通常由组件的渲染函数调用。

来源

创建过程包括

  1. 规范化输入的 props 和 children
  2. 确定 VNode 的类型并设置适当的标志
  3. 创建具有必要元数据的 VNode
  4. 可选地将节点跟踪在块树中以进行优化

块树机制

Vue 使用块树机制来优化更新。块是跟踪其动态子项的 VNode,这使得 Vue 在更新期间可以跳过树的静态部分。

来源

块树提供了多种优化

  • 在更新期间,仅对动态节点(由 dynamicChildren 跟踪)进行 diff
  • v-ifv-for 这样的结构指令会创建自己的块
  • 静态内容被“提升”,并在渲染之间重用

修补过程

当组件状态更改时,Vue 会创建一个新的 VNode 树并将其与之前的树进行比较。这个过程称为“打补丁”(patching),负责高效地更新 DOM。

来源

打补丁算法

  1. 比较新旧 VNode 类型
  2. 如果类型不同,则卸载旧节点并挂载新节点
  3. 如果类型相同,则用新属性更新现有节点
  4. 对于元素,根据优化标志处理子项
  5. 对于组件,触发生命周期钩子并根据需要更新

优化的打补丁策略

Vue 使用不同的策略来打补丁 VNode 树的不同部分

来源

打补丁过程通过多种方式进行了优化

  • 使用 shapeFlag 快速识别节点结构类型
  • 使用 patchFlag 只更新需要更新的特定属性
  • dynamicChildren 直接打补丁动态节点,跳过树遍历
  • 对不同类型的 VNode 进行特殊处理

与响应性系统的连接

当组件状态更改时,虚拟 DOM 由 Vue 的响应性系统触发。这两个系统之间的连接通过 effects 和 scheduler 建立。

来源

响应性被触发时

  1. 组件的更新 effect 被排队到 scheduler 中
  2. 当 scheduler 处理队列时,组件会重新渲染
  3. 生成新的 VNode 树
  4. patch 函数比较新旧树
  5. 应用最小的 DOM 更新

组件与虚拟 DOM 的集成

Vue 中的组件被表示为具有特殊处理的 VNode。组件实例维护一个指向其对应 VNode 和根 VNode 树的引用。

来源

组件与虚拟 DOM 的关系

  1. 每个组件实例都有一个 vnode 属性,指向其 VNode 表示
  2. subTree 属性指向组件渲染函数生成的 VNode 树
  3. 组件 VNode 具有 component 属性,指向其实例
  4. 组件更新会打补丁组件 VNode 及其子树

虚拟 DOM 中的 Props 处理

Props 通过 VNodes 从父组件传递到子组件。Vue 的 Props 处理包括验证、默认值和类型转换。

来源

Props 处理过程

  1. 父组件创建带有 props 的子 VNode
  2. 在子组件 setup 过程中,Props 被规范化
  3. 应用 Props 验证、默认值和类型转换
  4. Props 对子组件变得响应式
  5. 当 Props 更改时,子组件会重新渲染

性能优化

Vue 的虚拟 DOM 包含多种优化技术以提高性能

来源

关键优化技术

  1. Shape flags:快速识别节点结构类型
  2. Patch flags:只针对需要更新的特定属性
  3. Dynamic children tracking:更新时跳过稳定的子树
  4. Static hoisting:预先创建永不变动的节点
  5. Block tree:直接打补丁动态节点,绕过树遍历

生命周期集成

虚拟 DOM 与 Vue 的组件生命周期系统紧密集成,钩子在挂载和更新过程的特定阶段触发。

来源

Vue 的生命周期钩子在虚拟 DOM 操作的特定阶段触发

  1. beforeMount:在初始渲染应用于 DOM 之前
  2. mounted:在 DOM 被初始更新后
  3. beforeUpdate:在因响应性更改而重新渲染之前
  4. updated:在 DOM 被更新后
  5. beforeUnmount:在组件被从 DOM 中移除之前
  6. unmounted:在组件被完全移除后

跨平台渲染

虚拟 DOM 抽象允许 Vue 针对不同的渲染平台,而不仅仅是浏览器 DOM。

来源

渲染器 API 允许 Vue

  1. 将平台特定的操作抽象成渲染器实现
  2. 在不同平台使用相同的核心算法
  3. 支持非 DOM 环境的自定义渲染器
  4. 为服务器端渲染共享相同的虚拟 DOM 实现

总结

Vue 的虚拟 DOM 系统作为组件逻辑和实际 DOM 操作之间的强大抽象层。通过使用轻量级的 VNode 表示和高效的补丁算法,Vue 在提供声明式编程模型的同时,最大限度地减少了昂贵的 DOM 操作。该系统的优化,如块树机制和补丁标志,使 Vue 能够在保持灵活的组件模型的同时实现高性能。

虚拟 DOM 还通过抽象实际的渲染实现,使相同的代码库能够跨浏览器、服务器和自定义渲染目标工作,从而实现了 Vue 的跨平台功能。