Почему совместное редактирование сложно в мобильной среде?
Отметим: когда два инженера одновременно правят один документ, мобильная среда добавляет три сценария: нестабильная сеть с потерей пакетов (до 30% при плохом сигнале), фоновая синхронизация после офлайн-режима и нативная клавиатура с composition events — задержка ввода 300+ мс. Мы сталкивались с этим в корпоративном редакторе для 500+ пользователей, где пришлось балансировать между ACID-гарантиями и отзывчивостью UI. Наша команда реализует такие решения под ключ, используя проверенные алгоритмы. Опыт 5+ лет и 10+ проектов позволяет гарантировать стабильную синхронизацию даже при задержках сети до 2 секунд. Получите консультацию — мы оценим ваш проект и предложим архитектуру.
Выбор алгоритма синхронизации: OT vs CRDT
| Характеристика | OT (Operational Transform) | CRDT (Y.js, Automerge) |
|---|---|---|
| Координатор | Сервер обязателен | Опционально (работает в офлайн) |
| Офлайн-режим | Буферизация с последующим merge | Нативная поддержка идеальна |
| Производительность | Низкая задержка (<30ms) при стабильной сети | Эффективен при асинхронной синхронизации (50-100ms) |
| Сложность реализации | Средняя (серверная трансформация) | Низкая (нет центральной логики) |
| Использование памяти | ~10MB на 1000 операций | ~5MB на 1000 операций |
OT проверен в Google Docs и Apache Wave — сервер координирует операции, что исключает конфликты. CRDT (Y.js — стандарт де-факто) не требует сервера: merge работает локально. Для мобильного корпоративного редактора мы выбираем OT, для офлайн-first блокнота — CRDT в 2 раза быстрее по времени синхронизации.
Как Y.js решает проблему коллизий в React Native?
yjs — чистый JavaScript, работает в React Native без изменений. Типичная связка:
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). Интеграция с веб-редакторами через WebView даст быстрый MVP. Для нативного UX — кастомный TextInput с ручной синхронизацией через Y.js операции. На iOS — UITextViewDelegate, на Android — TextWatcher. Для ускорения используйте высоту строки 18sp и дебонс 200мс.
Курсоры и awareness
Y.js Awareness Protocol распространяет ephemeral-данные (курсоры, присутствие) между участниками. Не хранится в документе:
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 — нетривиально. Нужно конвертировать символьную позицию в пиксельные координаты через UITextView.caretRect(for:) на iOS и Layout.getDesiredWidth() на Android. Для этого используем Y.js Awareness и кастомные вычисления — точность до пикселя при 60fps.
Как разрешаются конфликты форматирования?
Y.js YText форматирование работает через Delta-операции с атрибутами. Конкурентное форматирование (один пользователь делает bold, другой italic на перекрытии) — оба атрибута применяются. Для семантически несовместимых операций (H1 vs H2) Y.js выбирает по clientId, но UI должен предупредить или дать ручной resolve. В одном проекте мы добавили модалку выбора — пользователи разрешают конфликт за 2 секунды.
Персистентность и история
Y.js документ сериализуется в Uint8Array через Y.encodeStateAsUpdate(). На мобиле храним в SQLite через react-native-sqlite-storage. При открытии: загружаем сохранённое состояние, применяем через Y.applyUpdate(), затем подключаемся к WebSocket для diff. Сервер использует y-leveldb или y-redis для persistence. Оптимизация: снэпшоты каждые 100 операций — время загрузки <500мс.
Технические детали реализации
Для серверной части достаточно минималистичного Node.js сервера из пакета y-websocket. В production стоит добавить persistence, auth и room access control.
Сравнение подходов: нативная vs WebView
| Критерий | Нативный TextInput + Y.js | WebView + Quill.js |
|---|---|---|
| Производительность | Высокая (60fps) | Средняя (30-45fps при 500 символов) |
| Сложность | Высокая (ручная синхронизация) | Низкая (готовая библиотека) |
| Поддержка форматирования | Ограниченная (кастомные кнопки) | Полная (панель инструментов) |
| Время MVP | 12–16 недель | 8–10 недель |
Что входит в работу
- Анализ сценариев использования и выбор алгоритма (OT/CRDT).
- Проектирование архитектуры синхронизации (WebSocket/WebRTC).
- Реализация текстового редактора с нативным рендерингом.
- Интеграция Y.js или OT-сервера.
- Разработка отображения чужих курсоров и awareness.
- Тестирование на реальных устройствах с эмуляцией плохой сети (потеря 30% пакетов).
- Документация и обучение команды.
- Поддержка после деплоя (1 месяц).
Ориентировочные сроки: MVP на React Native — 8–14 недель, нативные iOS/Android с богатым форматированием — 16–24 недели. Стоимость рассчитывается индивидуально.
Получите консультацию — оценим ваш проект. Закажите прототип за 2 недели. Напишите нам — покажем примеры кода.







