pinia 在协同编辑中仅作为本地协同状态的容器与协调枢纽,需结合 crdt/ot 等同步协议实现多人光标与选区一致收敛;关键字段包括 documentid、content、cursors、selections、isconnected、lastsynctime;光标与选区须由 pinia 统一管理并动态渲染,禁止存入 editorstate;更新需区分本地操作与远程同步,多文档场景下应按 documentid 隔离子 store 并及时销毁。

在协同编辑场景中,Pinia 本身不直接处理光标、选区或实时同步逻辑,它只负责本地状态的组织与响应式暴露。多人光标与协同状态的核心挑战在于:如何让多个用户对同一份文档的编辑操作(包括插入、删除、光标移动、选区变化)在不同客户端之间一致、无冲突地收敛。Pinia 的角色是作为**本地协同状态的容器与协调枢纽**,而非网络层或冲突解决引擎。要真正落地,需结合 CRDT(如 Yjs)、Operational Transformation(OT)库(如 ShareDB),或自研同步协议,并将协同结果映射到 Pinia 中可响应式消费的状态。
协同状态应存哪些关键字段
Pinia Store 中需定义明确、可序列化的协同元数据,避免把 Codemirror 实例或 DOM 引用塞进去:
- documentId:唯一标识当前协同文档(用于连接后端通道或 Yjs 文档)
- content:当前本地感知的文档内容(string 或 Text 类型,取决于底层同步库)
-
cursors:对象数组,每个元素含
userId、position(绝对偏移)、color、name;建议用ref或reactive包裹,便于响应式更新 -
selections:同理,存储其他用户的选区范围(
{ from, to }) - isConnected、connectionStatus:反映与协同服务的连通性,影响 UI 提示(如“离线”“正在重连”)
- lastSyncTime:辅助判断延迟或冲突风险
光标与选区必须脱离 EditorState 管理
Codemirror6 的 EditorState 是不可变且单机私有的。多人光标不能靠 state.selection 维护——那是本地视图状态,每次 update 都会覆盖。正确做法是:
- 所有远程光标/选区信息统一由 Pinia Store 管理,通过
store.cursors和store.selections暴露 - 在 Codemirror 的
ViewPlugin中监听update生命周期,读取 Store 中的cursors,用Decoration.set动态渲染其他用户光标(例如带头像+颜色的 vertical line) - 本地光标位置仍走 CM6 原生流程,但每次变更后需主动同步到后端,并触发
store.updateCursor({ userId: 'me', position: ... }) - 禁止在
EditorState中保存任何跨用户状态(比如把别人光标写进 extension)——这会导致切换标签页或重载时丢失
状态更新必须区分“本地操作”与“远程同步”
为避免无限循环和竞态,Pinia actions 要明确分离两类变更来源:
-
本地触发:用户输入、快捷键、菜单命令 → 调用
store.applyLocalChange()→ 发送 OT/CRDT 操作到服务端 → 服务端广播给其他人 -
远程到达:WebSocket/Yjs callback 收到他人变更 → 调用
store.mergeRemoteUpdate()→ 安全合并 content + cursors + selections → 触发响应式更新 - 两个 action 都应校验
documentId是否匹配,防止消息错投;mergeRemoteUpdate内部应做防抖(如 100ms 合并连续变更)和冲突降级(如丢弃过期光标)
多标签页下协同状态需按文档隔离
不要用一个全局 Store 管理所有协同文档。正确结构是:
- 主 Store(如
useCollabStore)只管连接状态、用户身份、全局配置 - 每个打开的协同文档对应一个动态创建的子 Store(如
useDocStore(docId)),用defineStore工厂函数生成,id 带文档标识 - 标签页关闭时,调用
store.$dispose()清理 WebSocket/Yjs 实例、取消插件监听,防止内存泄漏 - Pinia 插件支持模块化销毁,配合
storeToRefs解构可确保组件卸载后自动停止响应式订阅
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











