Реализация совместного редактирования документа в реальном времени в мобильном приложении
Совместное редактирование текста в реальном времени — одна из технически сложных задач в мобильной разработке. Не потому что алгоритмы сложны сами по себе, а потому что мобильная среда добавляет несколько измерений: нестабильная сеть, фоновый режим, нативная клавиатура с composition events, пользователи с задержкой 300ms+ из-за плохого сигнала.
Выбор алгоритма синхронизации
Два практических подхода: Operational Transform (OT) и CRDT (Conflict-free Replicated Data Type).
OT — проверен в Google Docs, Apache Wave. Операции (insert/delete с позицией) трансформируются на сервере с учётом конкурентных изменений. Сервер — координатор, все операции проходят через него. Преимущество: единственная история операций, нет конфликтов по определению. Недостаток: сервер обязателен, офлайн-работа требует буферизации с последующим merge.
CRDT — алгоритмы Automerge, Y.js, RGA (Replicated Growable Array). Нет центрального координатора, merge работает локально. Идеальны для офлайн-first сценариев, где пользователь редактирует документ без сети и синхронизируется позже. Y.js — стандарт де-факто для веба и React Native.
Для мобильного корпоративного редактора (Google Docs-подобный) — OT через WebSocket с сервером-координатором. Для коллаборативного блокнота с офлайн-режимом — Y.js + WebRTC или Y.js + WebSocket с провайдером синхронизации.
Y.js в React Native: реальная интеграция
yjs — чистый JavaScript, работает в React Native без модификаций. y-websocket — провайдер синхронизации через WebSocket. Типичная связка:
import * as Y from 'yjs';
import { WebsocketProvider } from 'y-websocket';
const ydoc = new Y.Doc();
const provider = new WebsocketProvider('wss://your-server.com/sync', 'doc-room-id', ydoc);
const ytext = ydoc.getText('document');
YText — CRDT-тип для текста с поддержкой форматирования (bold, italic, headers). Он интегрируется с Quill.js, ProseMirror, Slate.js на вебе. В React Native — через react-native-webview с Quill внутри, либо нативный текстовый редактор с ручной синхронизацией через Y.js операции.
Второй вариант сложнее, но даёт нативный UX. Кастомный TextInput в React Native не поддерживает beforeInput events — нужен NativeEventEmitter и перехват нативных keyboard events. На iOS — UITextViewDelegate, на Android — InputFilter или TextWatcher.
Курсоры и awareness
Y.js Awareness Protocol — легковесный механизм распространения ephemeral-данных (курсоры, присутствие, выделение текста) между участниками. Не хранится в документе, не влияет на CRDT-историю.
provider.awareness.setLocalState({
user: { name: 'Иван', color: '#3B82F6' },
cursor: { anchor: 45, focus: 45 }
});
provider.awareness.on('change', () => {
const states = Array.from(provider.awareness.getStates().values());
// обновляем позиции курсоров других пользователей
});
Отображение чужих курсоров в нативном TextInput — нетривиально. Нужно вычислять CGRect для позиции курсора через UITextView.caretRect(for:) на iOS и Layout.getDesiredWidth() / getLineTop() на Android. Позиция в символах → координаты в пикселях — разные API на каждой платформе.
Конфликты форматирования и разрешение
Y.js YText форматирование работает через Delta-подобные операции с атрибутами. Конкурентное форматирование (один пользователь делает текст bold, другой одновременно — italic на перекрывающемся диапазоне) — CRDT разрешает автоматически: оба атрибута применяются.
Сложнее с семантически несовместимыми операциями: один пользователь ставит заголовок H1 на абзац, другой — H2 на тот же абзац. Y.js выберет один из вариантов (по clientId), но UI должен об этом предупредить или дать инструмент ручного resolve.
Персистентность и история
Y.js документ сериализуется в Uint8Array через Y.encodeStateAsUpdate(). Для персистентности на мобиле: храним в SQLite через expo-sqlite или react-native-sqlite-storage. При открытии документа: загружаем сохранённое состояние, применяем через Y.applyUpdate(), затем подключаемся к WebSocket и получаем diff от сервера.
Сервер должен поддерживать y-leveldb или y-redis для хранения document state. y-websocket сервер — минималистичный Node.js сервер из пакета, подходит для начала. Для production: persistence + auth + room access control.
Оценка
Совместный редактор документов — комплексная задача. Срок MVP (базовый текст + синхронизация + курсоры) на React Native: 8–14 недель. Нативные iOS/Android с богатым форматированием — 16–24 недели. Существенная часть времени — нативный текстовый редактор, а не сетевая синхронизация.







