尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

从0到1实现一个实时协作备忘录:基于CRDT的协同编辑实践

从0到1实现一个实时协作备忘录:基于CRDT的协同编辑实践 想了解此项目的更新版本可点击下方链接CollabNote基于 Socket.IO 的实时协作备忘录设计与实现-CSDN博客一、缘起一个被忽视的协作场景先问大家一个问题当你需要和同事临时记录几个要点时通常怎么做最常见的做法是打开微信拉群发公告或者开个腾讯文档。但这两个方案都有痛点微信消息很快被淹没腾讯文档等工具需要登录很多人没有账号或懒得登录于是我做了一个小工具——CollabNote。地址CollabNote一句话总结一个无需登录、打开即用的实时协作备忘录。使用时只需三步打开页面创建备忘录点击「复制」获取分享链接把链接发给任何人所有人打开后内容实时同步不需要注册不需要登录不需要任何前置操作。二、技术选型为什么是CRDT实时协作的核心问题是冲突处理。当两个人同时编辑同一份文档A删除了第2行B在第3行加了几个字——如果只是简单按时间顺序覆盖结果一定是错的。业内主要有两种方案对比维度OT操作转换CRDT无冲突复制数据类型代表应用Google DocsFigma、Linear核心思路服务端转换冲突操作数据结构层面保证一致性实现复杂度复杂不同数据类型需不同逻辑相对模块化离线支持较难天然支持服务端依赖强依赖中心服务器支持P2P/客户端-服务器OT 的典型代表是 Google Docs但实现非常复杂。不同数据类型需要不同的转换函数且强依赖服务端进行冲突合并。CRDT 的代表是Yjs。其核心思想是每个字符或元素都有唯一ID删除用墓碑标记而非真正移除保证插入位置的相对性始终可追溯。无论操作以什么顺序到达最终结果一致。CollabNote 选择了 CRDTYjs方案。为什么实现更清晰Yjs 的 API 设计直观社区活跃有丰富的编辑器集成服务端轻量服务端只需转发和持久化操作冲突合并完全在客户端完成扩展性好后续可以轻松支持离线编辑等高级特性三、技术架构CollabNote 的技术栈text前端React TypeScript Vite 编辑器TipTap基于 ProseMirror 协同引擎YjsCRDT 通信协议WebSockety-websocket架构图简化版text┌─────────┐ WebSocket ┌─────────┐ │ 用户 A │ ──── 操作更新 ────→ │ │ └─────────┘ │ 服务端 │ │ (转发/ │ ┌─────────┐ │ 持久化) │ │ 用户 B │ ←─── 广播更新 ──── │ │ └─────────┘ └─────────┘四、核心实现4.1 Yjs TipTap 集成Yjs 官方提供了与 ProseMirrorTipTap 底层的集成方案核心代码非常精简typescriptimport * as Y from yjs import { WebsocketProvider } from y-websocket import { ySyncPlugin, yCursorPlugin } from y-prosemirror // 1. 创建 Yjs 文档CRDT 容器 const ydoc new Y.Doc() // 2. 连接协同服务器 const provider new WebsocketProvider( wss://your-server.com/collab, room-id-001, ydoc ) // 3. 获取共享数据结构 const yXmlFragment ydoc.getXmlFragment(prosemirror) // 4. 注入到 TipTap 编辑器 const editor new Editor({ extensions: [ // ... 其他扩展 YjsExtension.configure({ yDoc: ydoc, yXmlFragment: yXmlFragment, provider: provider, }), ], })4.2 自动保存机制自动保存采用500ms 防抖策略typescript// 500ms 防抖保存 const debouncedSave useCallback( debounce((content: string) { saveToServer(content) }, 500), [] ) // 监听内容变化 editor.on(update, ({ editor }) { const content editor.getHTML() debouncedSave(content) })500ms 是一个权衡值——太短服务端压力大太长用户感知延迟。同时保留CtrlS手动保存满足部分用户习惯。4.3 Awareness光标与在线状态Awareness 是协同系统中非持久化的临时状态包括光标位置、选区、用户在线状态等。typescript// 设置用户信息 provider.awareness.setLocalState({ user: { name: 用户A, color: #ff6b6b, }, cursor: { from: 10, to: 15 }, }) // 监听其他用户状态变化 provider.awareness.on(change, (changes) { // 更新界面上的光标显示 updateCursors(provider.awareness.getStates()) })Awareness 与文档内容走不同的通道文档内容需要持久化、强一致性通过 CRDT 同步Awareness不需要持久化、最终一致即可通过独立广播通道传输五、踩坑与经验5.1 光标位置保持当远程用户的更新到来时如果直接替换编辑器内容会导致当前用户的光标跳转。解决方案在应用远程变更前保存光标位置更新完成后恢复。TipTap 的setContent(content, false)中false参数用于阻止触发新的事件配合光标位置保存基本解决问题。5.2 删除确认「清空」和「删除」两个操作都需要二次确认。原因很简单——协作场景中别人可能在编辑你直接清空内容会严重影响体验。5.3 关于无需登录的权衡无需登录是 CollabNote 的核心体验优势但也带来一些代价没有用户身份无法做权限控制和历史追溯数据安全依赖用户自觉不建议记录敏感信息对于轻量级临时协作这个定位这些权衡是可接受的。六、后续计划更丰富的文本格式目前是纯文本后续支持 Markdown 或更富的格式历史版本回滚基于操作日志重放实现移动端适配完善移动端体验导出功能支持导出为 Markdown、TXT七、写在最后CollabNote 的初衷很简单做一个简单到不用思考的协作工具。不需要登录不需要学习打开就能用。它不会成为下一个 Notion但它填补了临时会议记录、头脑风暴、快速分享这个场景的空缺。如果你有类似的需求欢迎试试。如果你对技术实现感兴趣欢迎交流讨论。项目地址CollabNote欢迎在评论区留言交流觉得有用的话点个赞支持一下
返回列表