Published on

什么是 Delta 数据结构

Authors

Delta 的本质

Delta 是 一种面向操作(Operational Transformation, OT)的变更数据结构,用于描述 富文本的增量变更。

它并不是传统的存储结构(如数组、树、哈希表),而是一个有序的变更操作序列(Operations Array)。

在富文本编辑器 quill 和 slatejs 中,Delta 是一种常见的数据结构,用于描述文本的增量变更。Delta 本质上是一个数组,数组中的每个元素都是一个操作对象,用于描述文本的增量变更。

但是,Delta 并不仅仅是一个简单的数组,它还包含了一些元数据,比如文本的格式信息、样式信息等。这些元数据可以帮助编辑器正确地渲染文本,并保持文本的格式和样式。

Delta 的核心特点

基于操作的线性结构:使用 insert、delete、retain 记录文本和样式变更。 增量存储:不是存储一个完整的文档,而是存储文档变更的历史。

可以进行如下定义

interface Operation {
  insert?: string | object;
  delete?: number;
  retain?: number;
  attributes?: Record<string, any>;
}

type Delta = Operation[];

📜 Delta 的数据结构解析


🧐 1. Delta 的本质

Delta 是什么?

Delta一种面向操作(Operational Transformation, OT)的变更数据结构,用于描述 富文本的增量变更。 它并不是传统的存储结构(如数组、树、哈希表),而是一个有序的变更操作序列(Operations Array)


🌱 1.1 Delta 的核心特点

  • 基于操作的线性结构:使用 insertdeleteretain 记录文本和样式变更。
  • 增量存储:不存储完整文档,而仅记录从旧状态到新状态的 变更过程
  • 支持撤销/重做:每个操作都可以回放或反向应用,实现 undo/redo
  • 适用于协作编辑:与 CRDT、OT 算法兼容,便于多人编辑同步。

🌍 1.2 Delta 与传统数据结构的区别

特性Delta文本字符串DOM 结构
数据存储增量存储,仅记录变更存储完整内容结构化存储,含层次关系
操作方式基于 insert/delete/retain直接修改字符串通过 HTML 结构管理
撤销机制通过 invert() 反转操作需要手动维护版本DOM Diff 计算变化
协作能力适用于 CRDT、OT 算法不支持协作依赖 WebSocket 或 Diff 算法

🏗️ 2. Delta 的数据结构

📂 2.1 结构定义

Delta 是由多个 操作对象(Operation) 组成的 有序数组(Operations Array),其中每个操作都表示 对文档的一个变更

🔧 核心数据结构

interface Operation {
  insert?: string | object;
  delete?: number;
  retain?: number;
  attributes?: Record<string, any>;
}

type Delta = Operation[];

🎯 操作类型

操作类型作用示例
insert在指定位置插入文本或对象{ "insert": "Hello" }
delete删除指定长度的文本{ "delete": 5 }
retain跳过指定长度的文本(通常用于修改样式){ "retain": 6, "attributes": { "bold": true } }

📜 2.2 Delta 操作示例

🌟 示例 1:插入文本

[
  { "insert": "Hello " },
  { "insert": "World", "attributes": { "bold": true } }
]

解读

  • 插入 "Hello " 普通文本。
  • 插入 "World" 并加粗。

📝 示例 2:删除文本

假设当前文档是 "Hello World",我们想删除 "World"

[
  { "retain": 6 },
  { "delete": 5 }
]

解读

  • retain 6:跳过 "Hello "(不变)。
  • delete 5:删除 "World"

🎨 示例 3:修改样式

"Hello" 设为斜体:

[
  { "retain": 5, "attributes": { "italic": true } }
]

解读

  • retain 5:跳过 "Hello"
  • attributes:应用 italic 样式。

📌 示例 4:综合操作

假设我们从 "Hello World" 修改为:

  • 删除 "World"
  • 插入 "Flutter"
  • 加粗 Flutter
[
  { "retain": 6 },
  { "delete": 5 },
  { "insert": "Flutter", "attributes": { "bold": true } }
]

最终结果Hello **Flutter**


🔄 3. Delta 的变更合并(Compose)

Delta.compose() 用于合并两个 Delta 操作,减少冗余数据,提高存储效率。

🆘 未优化(两次插入)

[
  { "insert": "Hello" },
  { "insert": " World" }
]

优化后(合并)

[
  { "insert": "Hello World" }
]

🔀 4. Delta 的协作能力

在多人协作编辑中,Delta 可以通过 transform() 方法进行合并,避免冲突。

示例:两人同时编辑

  • 用户 A:在 "Hello" 之后插入 "Flutter"
  • 用户 B:将 "Hello" 加粗。

用户 A

[
  { "retain": 5 },
  { "insert": " Flutter" }
]

用户 B

[
  { "retain": 5, "attributes": { "bold": true } }
]

合并后的 Delta

[
  { "retain": 5, "attributes": { "bold": true } },
  { "insert": " Flutter" }
]

最终效果:**Hello** Flutter


🏆 5. Delta 的优势与局限性

5.1 优势

特性优势
可增量存储只存储变更,不存储完整文档
撤销/重做便捷通过 invert() 生成反向操作
协作编辑友好支持 CRDT / OT 变更合并
格式独立文本与样式分离,适用于多种富文本编辑器

5.2 局限性

问题解决方案
数据膨胀(增量存储过多)使用 Delta.compose() 进行合并
无法表示复杂结构(如表格)结合 Node Tree 存储嵌套结构
需要额外处理光标位置结合 Selection 结构进行同步

🎯 6. 结论:Delta 的本质

🔑 Delta 不是简单的数据存储结构,而是一个基于操作序列的富文本变更描述格式。 它通过 增量存储、撤销/重做支持、协作编辑优化,在富文本编辑器(如 Quill.js、AppFlowy)中具有广泛应用。

📌 关键总结

  • Delta 是有序的操作列表,描述文档的变更过程,而非最终内容。
  • 支持撤销/重做,能通过 invert() 还原操作。
  • 适用于多人协作编辑,能通过 transform() 解决冲突。
  • 可组合(compose)和优化,减少数据冗余,提升存储和渲染性能。

📌 核心洞察

“Delta 不是文档,而是文档的变更历史。” 🚀