如何确定OT文本差异?前端Sharedb(json0/text0)实现的技术问询
你的思路完全正确——ShareDB正是通过**操作转换(OT)**来实现多客户端的并发文本同步,直接提交完整文本会覆盖其他客户端的修改,而生成精准的OT差异操作才是正确的姿势。下面我会一步步给你实现方案和代码示例:
核心原理
text0类型是json0专门针对纯文本优化的子类型,它的OT操作格式非常简洁:
- 删除操作:
{ p: 起始位置, d: '要删除的文本' } - 插入操作:
{ p: 插入位置, i: '要插入的文本' }
我们需要做的就是在文本变化时,计算新旧文本的差异,把差异转换成上述格式的操作数组,再提交给ShareDB文档。
具体实现步骤
1. 准备依赖
首先需要一个文本差异计算库,推荐用diff-match-patch(Google开源的,处理文本diff非常靠谱):
npm install diff-match-patch
2. React组件实现示例
下面是一个完整的React组件示例,包含ShareDB文档初始化、文本变化时生成OT操作、同步其他客户端修改的逻辑:
import React, { useState, useEffect, useRef } from 'react'; import DiffMatchPatch from 'diff-match-patch'; import ShareDB from 'sharedb'; import { WebSocketJSON } from 'sharedb/lib/client'; // 初始化ShareDB连接和文档 const socket = new WebSocketJSON('ws://your-sharedb-server-url'); const connection = new ShareDB.Connection(socket); const doc = connection.get('your-collection', 'your-document-id'); const TextEditor = () => { const [text, setText] = useState(''); const isLocalUpdate = useRef(false); // 避免本地提交后触发循环更新 const dmp = useRef(new DiffMatchPatch()); // 初始化文档,加载远程文本 useEffect(() => { doc.subscribe((err) => { if (err) throw err; setText(doc.data?.text || ''); }); // 监听其他客户端的操作,更新本地文本 doc.on('op', (op) => { isLocalUpdate.current = true; // 应用OT操作到本地文本 const newText = ShareDB.types.text0.apply(text, op); setText(newText); isLocalUpdate.current = false; }); return () => { doc.unsubscribe(); }; }, []); // 将diff转换为text0的OT操作 const diffToOT = (oldText, newText) => { const diffs = dmp.current.diff_main(oldText, newText); const ops = []; let position = 0; for (const [type, value] of diffs) { if (type === 0) { // 文本无变化,跳过,位置累加长度 position += value.length; } else if (type === -1) { // 删除操作 ops.push({ p: position, d: value }); } else if (type === 1) { // 插入操作 ops.push({ p: position, i: value }); position += value.length; } } return ops; }; // 处理文本框的onChange事件 const handleChange = (e) => { const newText = e.target.value; if (isLocalUpdate.current) return; // 计算新旧文本的diff,生成OT操作 const ops = diffToOT(text, newText); if (ops.length > 0) { // 提交OT操作到ShareDB doc.submitOp(ops, (err) => { if (err) console.error('提交操作失败:', err); }); } // 本地先更新文本(也可以等ShareDB同步后再更新,看需求) setText(newText); }; return ( <textarea value={text} onChange={handleChange} rows={10} cols={50} /> ); }; export default TextEditor;
3. 关键注意点
- 避免循环更新:用
isLocalUpdate标记本地提交的操作,防止文档op事件触发后又调用handleChange。 - diff转换的准确性:
diff-match-patch的diff结果已经帮我们处理了文本的增删位置,我们只需要按顺序转换为text0的操作格式即可。 - 操作提交时机:如果是实时同步,建议在
onChange时立即提交;如果是大文本,可以考虑防抖后再提交,减少操作次数。 - 错误处理:
submitOp的回调要处理错误,比如网络断开、操作冲突等情况。
验证你的示例场景
针对你提到的场景:
- 原文本:
Hello World - 第一步删除第9位的
l:生成操作[{ p: 9, d: 'l' }] - 第二步在末尾添加
s:生成操作[{ p: 10, i: 's' }]
这两个操作提交后,其他客户端会依次应用,最终得到Hello Words,完全符合你的需求。
内容的提问来源于stack exchange,提问作者hopelessmuffins
相关产品推荐
相关产品推荐

