协同编辑(Yjs)设计与一致性策略
1. 总体设计
Plate 的协同不是“外部补丁”,而是插件化接入:
- 基础插件:
BaseYjsPlugin - 编辑器增强:
withPlateYjs - provider 适配:hocuspocus / webrtc 等
关键路径:
packages/yjs/src/lib/BaseYjsPlugin.tspackages/yjs/src/lib/withPlateYjs.ts
2. 初始化流程(高频)
BaseYjsPlugin 的 api.init 大致流程:
- 校验 provider 配置
- 实例化 provider 并绑定 connect/disconnect/sync 回调
- 可选自动连接,等待首次 sync(带 timeout)
- 判断远端 shared type 是否已有内容
- 仅在远端为空时灌入初始值(避免覆盖远端状态)
- 连接
YjsEditor,再走 editor init + onReady
这个顺序非常关键:先同步,再决定是否写入初始内容,避免“本地初始值覆盖远端文档”。
3. 一致性与可恢复性策略
3.1 空文档才写初始值
sharedRoot.length === 0 才会应用初始内容,这是一致性底线。
3.2 provider 统一生命周期管理
插件 API 提供:
connectdisconnectdestroy
并且针对 provider 类型有细节考虑(例如 awareness 清理时机)。
3.3 错误回调统一出口
provider 创建/连接中的错误统一走 onError,方便应用层监控。
4. 扩展性设计点
- provider 既支持“配置对象”,也支持“实例注入”
- 支持 custom shared type,不强制固定 Yjs 文档键
- 协同绑定顺序可控(核心 Yjs -> cursors -> history)
这让企业场景(自建服务、混合 provider、特殊同步策略)更容易接入。
5. 思考题:为什么协同也要插件化?
建议回答:
- 协同与编辑核心耦合深(selection/history/transform),不能仅靠外围订阅
- 插件化能复用 editor 生命周期与类型系统
- 可以按需启用,避免把协同成本强塞给单机场景
小结
Plate 的 Yjs 设计体现的是“协同即一等公民能力”:它不只是能连上服务,而是对初始化时序、一致性边界、provider 生命周期都做了工程化封装。