LoroDoc 入门
LoroDoc 是使用 Loro 的主要入口,负责容器管理、版本控制、事件系统以及导入导出等功能。
它提供:
- 容器管理——创建并管理 Text、List、Map、Tree、MovableList 等 CRDT 容器
- 版本控制——追踪历史、检出版本、管理分支
- 事件系统——订阅文档级或容器级的变更
- 导入导出——以多种格式保存与加载文档或增量
基础用法
首先创建一个 LoroDoc 实例:
// 使用随机 PeerID 创建文档
const = new ();
// 也可以设置固定 PeerID
.("1");
// 获取各类容器
const = .("text");
const = .("list");
const = .("map");
const = .("tree");
const = .("tasks");假设要建模如下结构:
{
"meta": {
"title": "Document Title",
"createdBy": "Author"
},
"content": "Article",
"comments": [
{
"user": "userId",
"comment": "comment"
}
]
}const = new ();
const = .("meta");
.("title", "Document Title");
.("createdBy", "Author");
.("content").(0, "Article");
const = .("comments");
const = .(0, new ());
.("user", "userId");
.("comment", "comment");支持的数据类型(LoroValue)
LoroValue 表示 Loro 容器可直接存储的类型:
type LoroValue =
| null
| boolean
| number
| string
| Uint8Array
| LoroValue[]
| { [key: string]: LoroValue };存储不同数据类型的示例
const = new ();
const = .("data");
// 基本类型
.("name", "Alice");
.("age", 30);
.("score", 95.5);
.("isActive", true);
.("metadata", null);
// 二进制数据
.("bytes", new ([1, 2, 3, 4, 5]));
// 嵌套对象
.("profile", {
: "[email protected]",
: {
: "dark",
: true,
},
});
// 数组
.("tags", ["javascript", "typescript", "loro"]);
// 混合结构
.("complex", {
: [1, 2, { : true }],
: new ([255, 128]),
: false,
});如果需要在容器内嵌套另一个 CRDT 容器(例如 LoroText、LoroList),请使用
setContainer()或insertContainer(),而不是普通的set/insert。
容器类型
LoroDoc 支持以下容器:
- Text:富文本编辑
- List:有序集合
- Map:键值对
- Tree:树形结构
- MovableList:可移动列表
Text
const = new ();
const = .("text");
.(0, "Hello");
.(5, " World!");
.(.()); // "Hello World!"
// 富文本
.({
: { : "after" },
: { : "none" },
});
.({ : 0, : 5 }, "bold", true);List
const = new ();
const = .("list");
.(0, "first");
.(1, "second");
.(.()); // ["first", "second"]
const = .(2, new ());
.(0, "nested text");Map
const = new ();
const = .("map");
.("name", "John");
.("age", 30);
.(.("name")); // "John"
const = .("bio", new ());
.(0, "Software Engineer");Tree
const = new ();
const = .("tree");
const = .();
..("name", "Root");
const = .();
..("name", "Child 1");
const = .();
..("name", "Child 2");MovableList
const = new ();
const = .("tasks");
.(0, "Task 1");
.(1, "Task 2");
.(0, 1); // 将 Task 1 移到 Task 2 之后协作同步
const = new ();
.("1");
const = .("text");
const = new ();
.("2");
const = .("text");
.(() => {
.();
});
.(() => {
.();
});
.(0, "Hello");
.();
await .();
.(5, " World!");
.();撤销 / 重做
const = new ();
const = new (, {
: 100,
: 1000,
});
const = .("text");
.(0, "Hello");
.();
if (.()) {
.();
}
if (.()) {
.();
}导入与导出
const = new ();
const = .({ : "snapshot" });
const = .();
const = new ();
.();浅快照
const = new ();
const = .({
: "shallow-snapshot",
: .(),
});
const = .();
const = .();
const = .();浅快照只包含某一版本点之后的历史,适合减小数据体积或隐藏旧历史。
脱敏敏感内容
const = new ();
.("1");
const = .("text");
.(0, "Sensitive information");
.();
const = .("map");
.("password", "secret123");
.("public", "public information");
.();
const = .();
const = {
"1": [0, 21],
};
const = (, );
const = new ();
.();
.(.("text").());
// "���������������������"脱敏会替换文本为占位符,Map/List 插入值为 null,但保留结构与元数据。若需彻底移除历史,请结合浅快照。
记得将脱敏后的文档同步给所有节点,否则仍可能存在包含敏感信息的旧副本。
订阅事件
const = new ();
.(() => {
.("Document changed:", );
});
const = .("text");
.(() => {
.("Text changed:", );
});事件触发
事件在事务提交后、微任务结尾时触发:
const = new ();
const = .("text");
.(() => {
.("Change event:", );
});
.(0, "Hello");
.();
await .();导出、导入、checkout 等操作会隐式提交:
const = new ();
.({ : "snapshot" });
.();
.();提交时可以携带额外信息:
const = new ();
.({
: "user-edit",
: "Add greeting",
: .(),
});
await .();版本控制与历史
版本表示
- 版本向量(peer → counter):
const = new ();
const = .();
const = .();- Frontiers(各 peer 最新操作 ID,通常只有 1 个元素):
const = new ();
.("0");
.("map").("text", "Hello");
const = .();
const = .();checkout 与时光旅行
const = new ();
const = .();
const = .("text");
.(0, "Hello World!");
.();
.();
// 或 doc.attach();checkout 后文档进入 detached 状态:默认不可编辑、导入的操作不会立即应用,需要 attach() 回到最新版本。
可以显式控制 detached:
const = new ();
.(.()); // false
.();
.(.()); // true
.();
.(.()); // false
.(true);
.(.()); // true导入行为
const = new ();
.("map").("name", "John");
const = .({ : "update" });
const = new ();
.(); // 若处于 detached,仅记录,不立即应用
.(); // 应用更新fork
const = new ();
.("0");
.("text").(0, "Hello");
const = .();
const = .([{ : "0", : 1 }]);
.(.("text").()); // "He"订阅与同步
本地更新订阅
const = new ();
const = .(() => {
.();
});
();文档事件
const = new ();
.((: LoroEventBatch) => {
.("触发来源:", .);
.("origin:", .);
for (const of .) {
.("容器:", .);
.("路径:", .);
.("diff:", .);
}
});指定容器事件
const = new ();
const = .("text");
.(() => {
.("Text changed:", );
});
const = .("list");
.(() => {
.("List changed:", );
});高级能力
稳定光标
const = new ();
const = .("text");
.(0, "123");
const = .(0, 0);
if () {
const = .();
.(.);
.(.);
.(0, "abc");
const = .();
.(.);
}变更追踪
const = new ();
.("1");
.("text").(0, "Hello");
.();
.(.());
.(.());
const = .();
for (const [, ] of .()) {
for (const of ) {
.({
: .,
: .,
: .,
: .,
: .,
});
}
}
const = { : "1", : 0 } as ;
const = .();
const = .();
.([], () => {
.("Ancestor:", );
return true;
});
const = .(, 1);导入 / 导出模式
const = new ();
const = .();
.("text").(0, "Hello");
const = .({ : "snapshot" });
const = .({ : "update", : });
const = .({
: "shallow-snapshot",
: .(),
});
const = .({
: "updates-in-range",
: [{ : { : "1", : 0 }, : 10 }],
});
const = .();
.(.);
.(.);
const = .([, ]);
const = .({
: 1,
: new ([["1", 0]]),
: ["1"],
: [],
});路径访问
const = new ();
const = .("map/key");
const = .("list");
const = .("cid:root-list:List");
const = .("$.list[*]");
const = .();
.();
const = .((, ) => {
if ( instanceof ) {
return .();
}
return ;
});调试与元信息
const = new ();
();
const = .({ : "update" });
const = (, true);
.({
: .,
: .,
: .,
: .,
});