Проблема ad‑hoc обработки соединения
В большинстве современных веб‑приложений сетевое взаимодействие реализовано «на лету»: каждый компонент, который отправляет запрос, самостоятельно проверяет статус сети и реагирует на ошибки. На первый взгляд такой подход выглядит простым, но в реальном мире он приводит к неожиданным потерям данных. Представьте, что пользователь редактирует документ в поезде, связь прерывается, он нажимает «Сохранить», а приложение лишь показывает пустой диалог «Вы офлайн» и ничего не сохраняет. После перезагрузки изменения исчезают. Причина кроется в том, что приложение считает себя онлайн, полагаясь исключительно на navigator.onLine, который часто возвращает true, даже когда реального доступа к серверу нет.
Типичные недостатки текущих подходов
- Разрозненный UI для офлайн‑состояния – каждый компонент реализует собственный индикатор или блокировку, из‑за чего пользователь сталкивается с несогласованным поведением.
- Повторяющаяся логика повторных попыток – в разных местах кода пишутся свои стратегии ретраев, экспоненциального бэкофа или дедупликации запросов. Это приводит к дублированию и ошибкам.
- Отсутствие дедупликации – быстрые последовательные клики по кнопке «Сохранить» могут породить несколько одинаковых запросов, увеличивая нагрузку и создавая конфликты.
- Неправильные сигналы о состоянии сети –
navigator.onLineне учитывает случаи, когда сеть недоступна, но устройство считает себя онлайн (например, Wi‑Fi без выхода в интернет).
Все эти проблемы усиливаются в больших проектах, где сетевые операции распределены по множеству компонентов.
Три базовых примитива connectivity‑js
Библиотека connectivity-js предлагает единый набор примитивов, которые решают перечисленные недостатки:
<Connectivity>– декларативный обёртка, отвечающая за переключение UI между онлайн‑ и офлайн‑режимами.useConnectivity()– хук, предоставляющий реактивный доступ к статусу сети и методам управления очередью запросов.createRequest()– типобезопасный конструктор запросов, автоматически интегрирующийся в очередь и поддерживающий ретраи, дедупликацию и отложенное выполнение.
Эти примитивы работают совместно, образуя слой, который можно подключать к любой части приложения без излишних boilerplate‑кодов.
Декларативный компонент <Connectivity>
import { Connectivity } from '@connectivity-js/react';
<Connectivity fallback={<OfflineScreen />} delayMs={2000}>
<App />
</Connectivity>
Компонент рендерит дочерние элементы только при подтверждённом онлайн‑состоянии. Параметр fallback задаёт UI, отображаемый при отсутствии соединения, а delayMs вводит задержку перед переключением, устраняя «мигание» UI в случае кратковременных сбоев. При сервер‑сайд рендеринге (SSR) неизвестный статус считается онлайн, что предотвращает рассинхрон гидратации.
<Connectivity> можно вкладывать, ограничивая область действия офлайн‑логики: отдельные панели, модальные окна или даже отдельные запросы могут иметь собственные fallback‑компоненты.
Хук useConnectivity и типобезопасность
import { useConnectivity } from '@connectivity-js/react';
function SaveButton({ data }: { data: Document }) {
const { isOnline, enqueue } = useConnectivity();
const handleSave = async () => {
if (!isOnline) {
alert('Сейчас вы офлайн');
return;
}
await enqueue(() => api.saveDocument(data));
};
return <button onClick={handleSave}>Сохранить</button>;
}
Хук возвращает флаг isOnline и функцию enqueue, которая помещает переданную задачу в очередь запросов. enqueue гарантирует, что запрос будет выполнен только при доступном соединении, автоматически повторит его в случае временных ошибок и объединит повторные вызовы с одинаковыми параметрами.
Благодаря TypeScript‑поддержке, enqueue принимает функцию, возвращающую Promise<T>, а тип T выводится автоматически. Это устраняет необходимость в кастах и облегчает статический анализ кода.
Работа с запросами и очередь
createRequest формирует объект запроса с предустановленными стратегиями:
import { createRequest } from '@connectivity-js/core';
const saveDoc = createRequest({
fn: api.saveDocument,
deduplicate: true,
retry: { attempts: 3, backoff: 500 },
});
- Дедупликация: если в очередь попадает несколько запросов с одинаковыми аргументами, сохраняется только последний.
- Ретраи: задаётся количество попыток и базовый интервал бэкофа; библиотека автоматически увеличивает задержку между попытками.
- Отложенное выполнение: запросы помещаются в очередь и запускаются, когда
isOnlineпереходит вtrue.
Очередь сохраняется в памяти, но её можно конфигурировать для синхронизации с IndexedDB или localStorage, что позволяет восстанавливать незавершённые операции после полной перезагрузки страницы.
SSR и гибкая конфигурация
Для сервер‑сайд рендеринга connectivity-js предлагает Provider с параметром initialOnlineState. На сервере обычно передаётся true, чтобы избежать несовпадения HTML‑структур. На клиенте библиотека автоматически подписывается на события online/offline и корректирует состояние.
Конфигурацию можно задать глобально:
import { ConnectivityProvider } from '@connectivity-js/react';
<ConnectivityProvider
retryDefaults={{ attempts: 5, backoff: 1000 }}
deduplication={true}
>
<App />
</ConnectivityProvider>
Таким образом, все компоненты наследуют единые правила без необходимости дублировать их в каждом месте.
Практические рекомендации по интеграции
- Оборачивайте верхний уровень приложения в
<Connectivity>– это гарантирует, что весь UI будет согласованно переключаться между онлайн и офлайн режимами. - Перенесите всю сетевую логику в
createRequest– таким образом, каждый запрос автоматически получит ретраи, дедупликацию и очередь. - Используйте
useConnectivityтолько для чтения статуса – любые действия, связанные с сетью, должны проходить черезenqueueили готовый запрос. - Настройте персистентную очередь – если приложение работает в условиях длительных отключений, сохранение запросов в
IndexedDBпредотвратит их потерю. - Тестируйте сценарии потери соединения – имитируйте отключения в dev‑режиме, проверяя, что UI отображает fallback‑компоненты и запросы правильно переигрываются после восстановления сети.
Применяя эти практики, разработчики получают надёжный offline‑first слой без необходимости писать повторяющийся boilerplate‑код, повышая устойчивость продукта и защищая данные пользователей от потери при нестабильных сетевых условиях.