容器
容器是 Loro 用于组织和构建协作数据的基础组件。它们提供带类型的数据结构,在发生并发编辑时会自动合并。
容器类型
Loro 提供多种容器类型,针对不同场景进行了优化:
- LoroMap:带有最后写入生效(Last-Write-Wins)语义的键值对
- LoroList:有序序列,可合并并发插入
- LoroText:字符级合并并支持富文本
- LoroTree:支持移动操作的层级树结构
- LoroMovableList:可重新排序的列表
- LoroCounter:支持增减操作的数值容器
容器状态:Attached 与 Detached
在 Loro 中,容器存在两种状态,不同状态会影响行为和标识。
Detached 容器
通过构造函数直接创建的容器处于 detached 状态:
// 这些容器都处于 detached 状态
const = new ();
const = new ();
const = new ();Detached 容器的特点:
- 尚未加入任何文档
- 拥有默认的占位 ContainerID
- 可作为模板或临时数据结构使用
- 插入文档后才会获得正式的 ContainerID
Attached 容器
当容器成为文档层级的一部分时,即进入 attached 状态:
const = new ();
// 根容器创建后立即是 attached 状态
const = .("myMap");
const = .("myText");
// 子容器:返回值是 attached 状态
const = new ();
const = .("child", );
// 注意:detachedChild 仍然是 detached
// attachedChild 拥有正确的 ContainerIDAttached 容器的特点:
- 隶属于某个具体文档
- 拥有唯一标识它的 ContainerID
- 变更会记录在文档历史中
- 可以在节点之间同步
容器 ID
每个 attached 容器都会在分布式系统中拥有唯一的 ContainerID。ID 的生成取决于容器类型:
- 根容器:依据名称生成(例如
doc.getMap("myMap")中的 “myMap”) - 子容器:基于创建它的操作(OpID)生成
这种确定性的 ID 生成策略确保:
- 所有节点都能识别出同一个容器
- 容器 ID 不是随机的,而是由上下文决定
- Detached 容器在插入前无法获得最终 ID
操作容器
创建根容器
根容器通过文档 API 创建,创建后立即处于 attached 状态:
const = new ();
// 这些方法用于创建或获取根容器
const = .("settings");
const = .("content");
const = .("items");
const = .("hierarchy");嵌套容器
容器可以相互嵌套,构建复杂的数据结构:
const = new ();
const = .("root");
// 方法 1:使用 setContainer(返回 attached 容器)
const = .("description", new ());
// 方法 2:对于列表使用 insertContainer
const = .("items");
const = .(0, new ());容器覆盖
在并行初始化子容器时,Loro 会发生覆盖而非自动合并。例如:
const : string = "hello";
const = new ();
const = .("map");
// 并行初始化子容器
const = .();
const = .("map").("text", new ());
.(0, "A");
const = .("map").("text", new ());
.(0, "B");
.(.({ : "update" }));
// 结果:要么 { "meta": { "text": "A" } },要么 { "meta": { "text": "B" } }如果没有保存编辑历史,这种行为会带来严重的数据丢失风险。即便保留了完整历史能够回滚恢复,操作流程也会非常复杂。
当容器承载大量数据,或是文档的主存储区域时,覆盖会隐藏或丢失关键信息。因此需要在容器初始化阶段采取谨慎、系统化的策略,避免出现问题。
最佳实践
- 如果可能发生并发初始化,尽量在根层级初始化容器,而不是作为嵌套容器
- 当使用 Map 容器时:
- 尽量在创建 Map 容器时一次性初始化所有子容器
- 避免在 Map 中并发创建同名子容器,以防止覆盖
出现覆盖是因为并行创建子容器会生成不同的 ContainerID,导致无法自动合并内容。
相关概念
- Container ID:深入了解 ContainerID 的工作机制
- 选择 CRDT 类型:如何挑选合适的容器类型
- 组合:如何组合容器构建复杂结构