Урок по треку React Native 7 мин

Офлайн и хранение данных

AsyncStorage, MMKV, SQLite, защищённое хранилище, очередь офлайн-запросов и конфликты.

Мобильная сеть пропадает в метро, лифте и самолёте. Хорошее приложение показывает сохранённые данные, принимает действия без сети и отправляет их, когда связь вернётся. Вопросы на собесе: где хранить данные, как хранить секреты и как синхронизировать офлайн-изменения.

Где хранить

ХранилищеЧто этоДля чего
AsyncStorageасинхронное ключ-значение, строкинебольшие настройки, флаги
MMKVбыстрое синхронное ключ-значение на C++ через JSIнастройки, кеш, состояние стора
SQLite (expo-sqlite, op-sqlite)реляционная базабольшие объёмы, запросы, офлайн-данные
WatermelonDB, Realmбазы с реактивными запросами и синхронизациейсложный офлайн-first
Keychain / Keystore (expo-secure-store, react-native-keychain)защищённое хранилище ОСтокены, пароли, ключи
Файловая системафайлыизображения, документы, большие ответы

AsyncStorage не шифрует данные и не подходит для больших объёмов: значения это строки, всё приходится сериализовать в JSON. MMKV работает синхронно и в разы быстрее, поэтому его часто используют как хранилище для персистентности Zustand или Redux. Синхронный доступ удобен для чтения настроек при старте, но писать в него мегабайты на каждое нажатие всё равно не стоит.

Секреты

Токены доступа и refresh-токены хранят только в Keychain (iOS) и Keystore (Android). AsyncStorage и MMKV без шифрования лежат файлом в песочнице приложения: на рутованном устройстве или из резервной копии их можно прочитать. Ключи сторонних сервисов с серверными правами в приложение не кладут вообще: всё, что в бинарнике, считается публичным.

Состояние сети

@react-native-community/netinfo сообщает тип подключения и isConnected/isInternetReachable. Но «есть Wi-Fi» не значит «есть интернет»: надёжный признак это успешный запрос. Поэтому офлайн-логика опирается на ошибки сети, а NetInfo служит подсказкой, когда пора повторить.

Офлайн-очередь

Пользователь без сети отметил задачу выполненной. Схема:

  1. Сразу применить изменение локально (оптимистично) и показать его.
  2. Положить операцию в постоянную очередь (SQLite или MMKV), чтобы она пережила перезапуск.
  3. Когда сеть есть, отправлять операции по порядку. Успех: удалить из очереди. Сетевая ошибка: остановиться и повторить позже с экспоненциальной задержкой. Ошибка валидации (4xx): не повторять бесконечно, а пометить и показать пользователю.
  4. Каждой операции дать ключ идемпотентности (UUID), чтобы повтор после обрыва не создал дубль на сервере.

Конфликты

Пока устройство было офлайн, ту же запись поменяли на другом устройстве. Стратегии:

  • Last write wins: побеждает изменение с более поздней меткой времени. Просто, но тихо теряет данные. Метки времени клиента ненадёжны, лучше версии или время сервера.
  • Версии записей: сервер отклоняет изменение со старой версией (409 Conflict), клиент получает свежие данные и решает: слить автоматически или спросить пользователя.
  • Слияние по полям: если изменены разные поля, их можно объединить.
  • CRDT: структуры, которые сливаются без конфликтов, для совместного редактирования.

Кеш серверных данных

TanStack Query с персистером (через MMKV или AsyncStorage) сохраняет кеш на диск: после перезапуска приложение сразу показывает последние данные и тихо обновляет их. Для больших наборов данных и поиска нужна полноценная база.

Частые ошибки

  • Токены в AsyncStorage.
  • Огромный JSON в одном ключе AsyncStorage, который читается целиком на каждый чих.
  • Очередь в памяти: операции теряются при закрытии приложения.
  • Повтор без идемпотентности: двойные заказы.
  • Доверять isConnected больше, чем результату запроса.

Что спросят на собесе

  • AsyncStorage, MMKV, SQLite: когда что.
  • Где хранить токены и почему.
  • Как спроектировать офлайн-режим для приложения задач.
  • Как решать конфликты синхронизации.
  • Зачем ключ идемпотентности.

Закрепите за 5 минут

После урока в тренажёре 3-5 проверочных вопросов, дальше модуль уходит в повторения.