You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何确定OT文本差异?前端Sharedb(json0/text0)实现的技术问询

实现React中ShareDB text0类型的OT文本同步操作

你的思路完全正确——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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 10:09:21