Хранение данных в React Native 2026: AsyncStorage, MMKV, expo-sqlite и Drizzle ORM
Полный разбор хранения данных в React Native 2026: AsyncStorage против MMKV, expo-sqlite в SDK 53, Drizzle ORM, шифрование, бенчмарки и пошаговая миграция с AsyncStorage на MMKV.
Хранение данных в React Native 2026 сводится к выбору между четырьмя проверенными решениями: AsyncStorage для простых пар ключ-значение, MMKV для синхронного хранилища с шифрованием, expo-sqlite для структурированных реляционных данных и Drizzle ORM поверх SQLite для типобезопасных запросов. AsyncStorage всё ещё считается стандартом по умолчанию, но MMKV работает до 30 раз быстрее и стал предпочтительным выбором в большинстве новых проектов. Для сложных моделей данных связка expo-sqlite с Drizzle ORM теперь является каноничной заменой WatermelonDB и Realm.
MMKV 3.x использует JSI и работает синхронно. На массовых операциях это в 20–30 раз быстрее AsyncStorage, и библиотека совместима с Новой архитектурой React Native 0.79+.
AsyncStorage всё ещё подходит для маленьких объёмов данных (меньше 2 МБ) и для сторонних SDK, которые требуют именно его API.
expo-sqlite в SDK 53 поставляется с новым API на основе обещаний, поддержкой WAL и встроенным шифрованием через SQLCipher.
Drizzle ORM 0.39 интегрируется с expo-sqlite через драйвер drizzle-orm/expo-sqlite и поддерживает миграции через drizzle-kit.
Для секретов (JWT, refresh-токены, ключи API) используйте expo-secure-store. Он сохраняет данные в Keychain на iOS и Keystore на Android.
В 2026 году WatermelonDB и Realm уходят на второй план в пользу связки SQLite с Drizzle или op-sqlite с Drizzle.
Сравнение хранилищ React Native 2026
Прежде чем погружаться в код, полезно увидеть все четыре варианта рядом. Таблица ниже отражает реальные показатели для типичного приложения на React Native 0.79 с включённой Новой архитектурой (Fabric, TurboModules, Bridgeless). Цифры взяты из бенчмарков Marc Rousavy (автор MMKV) и подтверждены замерами на Pixel 8 и iPhone 15 в марте 2026 года.
Характеристика
AsyncStorage
MMKV 3.x
expo-sqlite
Drizzle + expo-sqlite
API
Асинхронный (Promise)
Синхронный (JSI)
Promise + sync
Promise (типизирован)
Скорость записи 10к ключей
~ 8 000 мс
~ 280 мс
~ 600 мс
~ 750 мс
Размер бандла
~ 6 КБ
~ 50 КБ
~ 120 КБ
~ 180 КБ
Шифрование
Нет
AES-128 (опц.)
SQLCipher (опц.)
SQLCipher (опц.)
Реляционные запросы
Нет
Нет
SQL
SQL + типы TS
Новая архитектура
Да (v2.1+)
Да (нативно)
Да
Да
Лимит размера
~ 6 МБ (Android)
Без лимита
Без лимита
Без лимита
Сложность
Низкая
Низкая
Средняя
Средняя
Из таблицы видно главное: AsyncStorage проигрывает MMKV на порядок по скорости и при этом не поддерживает шифрование «из коробки». Но он остаётся в экосистеме, потому что десятки SDK (Firebase, Sentry, Amplify, RevenueCat) до сих пор используют его как обязательный backing store. Поэтому в реальных проектах 2026 года вы почти всегда увидите комбинацию: MMKV для UI-состояния и пользовательских настроек, AsyncStorage в зависимостях, а expo-sqlite для бизнес-данных.
Какое хранилище данных лучше для React Native?
Короткий ответ: зависит от формы данных и частоты операций. Если вы храните меньше 50 пар ключ-значение и не критична скорость старта, AsyncStorage достаточно. Если данные читаются на каждом ререндере (например, тема, язык, фича-флаги), берите MMKV ради синхронности. А вот если у вас списки пользователей, чатов, заказов или офлайн-кеш API, тогда уже SQLite через expo-sqlite или op-sqlite. И если поверх SQLite вы хотите автокомплит и компиляцию запросов в TypeScript, добавляйте Drizzle ORM.
Честно говоря, в моей практике типичная архитектура хранилища 2026 года выглядит так. Я использую MMKV для слайсов Zustand и для кеша TanStack Query (это даёт мгновенную регидратацию состояния при холодном запуске). Поверх expo-sqlite я кладу Drizzle, чтобы вынести запросы из компонентов в отдельный слой репозиториев. Для аутентификационных токенов только expo-secure-store, никогда не MMKV без шифрования. Если вам интересна интеграция MMKV со Zustand, посмотрите наш разбор в материале про управление состоянием в React Native 2026.
AsyncStorage: когда он всё ещё актуален
AsyncStorage версии 2.1 в начале 2026 года перешёл на новую архитектуру и Fabric, но остался асинхронным по дизайну. Это значит, что любое чтение возвращает Promise, и значение нельзя получить во время первого рендера без эффекта или suspense-обёртки. На Android он хранит данные в SQLite, на iOS в файле. Лимит размера на Android составляет около 6 МБ. На iOS лимита нет, но при превышении 10 МБ заметно деградирует производительность.
Установка минимальна и работает в любом проекте Expo или bare React Native:
import AsyncStorage from '@react-native-async-storage/async-storage';
const THEME_KEY = '@app/theme';
export async function saveTheme(theme: 'light' | 'dark') {
try {
await AsyncStorage.setItem(THEME_KEY, theme);
} catch (e) {
console.warn('Failed to persist theme', e);
}
}
export async function loadTheme(): Promise<'light' | 'dark'> {
const value = await AsyncStorage.getItem(THEME_KEY);
return value === 'dark' ? 'dark' : 'light';
}
Когда AsyncStorage всё ещё разумный выбор? Если вы только начинаете проект и не хотите добавлять нативный модуль. Или пишете маленькую утилиту. Или интегрируетесь с библиотекой, которая жёстко требует именно AsyncStorage API (например, redux-persist или Firebase Auth для веба). В остальных случаях переходите на MMKV.
MMKV 3.x: синхронное хранилище ключ-значение
MMKV это библиотека от Tencent, портированная на React Native командой Marc Rousavy. Версия 3.x работает поверх JSI и обходит мост, поэтому операции выполняются синхронно. Это ключевое отличие: значение доступно мгновенно, без await и без эффектов на маунте компонента.
Установка:
npx expo install react-native-mmkv
# Для bare React Native: cd ios && pod install
Создание инстанса хранилища и базовые операции:
import { MMKV } from 'react-native-mmkv';
export const storage = new MMKV({
id: 'user-storage',
encryptionKey: 'optional-aes-key-here',
});
storage.set('user.name', 'Иван');
storage.set('user.age', 28);
storage.set('user.isActive', true);
const name = storage.getString('user.name'); // 'Иван', синхронно
const age = storage.getNumber('user.age'); // 28
const active = storage.getBoolean('user.isActive'); // true
storage.delete('user.name');
storage.clearAll();
Для интеграции с React используйте хук useMMKVString, useMMKVNumber или useMMKVBoolean. Они реактивно обновляют компонент при изменении ключа в другой части приложения:
Когда данных много или они реляционные, ключ-значение уже не масштабируется. expo-sqlite в SDK 53 (релиз февраля 2026) предлагает современный API на промисах, поддержку WAL (Write-Ahead Logging) для параллельных чтений, синхронные методы для горячих путей и опциональное шифрование через SQLCipher. Подробности см. в официальной документации expo-sqlite.
Установка и открытие базы:
npx expo install expo-sqlite
import * as SQLite from 'expo-sqlite';
const db = await SQLite.openDatabaseAsync('app.db', {
enableChangeListener: true,
useNewConnection: false,
});
await db.execAsync(`
PRAGMA journal_mode = WAL;
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY NOT NULL,
name TEXT NOT NULL,
email TEXT UNIQUE,
created_at INTEGER NOT NULL
);
`);
await db.runAsync(
'INSERT INTO users (name, email, created_at) VALUES (?, ?, ?)',
['Анна', '[email protected]', Date.now()]
);
const users = await db.getAllAsync<{ id: number; name: string }>(
'SELECT id, name FROM users WHERE name LIKE ?',
['А%']
);
Для редких задач с большим объёмом изменений оборачивайте операции в транзакцию. Это даёт прирост скорости в 5–10 раз и гарантирует атомарность:
await db.withTransactionAsync(async () => {
for (const item of bigArray) {
await db.runAsync('INSERT INTO items (data) VALUES (?)', [item]);
}
});
Хук useSQLiteContext и SQLiteProvider позволяют пробрасывать соединение через дерево компонентов без props-drilling и автоматически закрывают базу при размонтировании.
Drizzle ORM поверх expo-sqlite
Голые SQL-запросы быстро превращаются в источник багов: опечатки в названиях столбцов, ручная сериализация JSON, отсутствие автокомплита. Drizzle ORM решает это, не превращаясь в тяжёлый Active Record, как Realm или TypeORM. Она остаётся «тонкой»: генерирует SQL на этапе компиляции и сохраняет полный контроль над запросами. Драйвер drizzle-orm/expo-sqlite вышел стабильным в декабре 2025.
// db/schema.ts
import { sqliteTable, integer, text } from 'drizzle-orm/sqlite-core';
export const users = sqliteTable('users', {
id: integer('id').primaryKey({ autoIncrement: true }),
name: text('name').notNull(),
email: text('email').unique(),
createdAt: integer('created_at').notNull(),
});
export type User = typeof users.$inferSelect;
export type NewUser = typeof users.$inferInsert;
// db/client.ts
import { drizzle } from 'drizzle-orm/expo-sqlite';
import * as SQLite from 'expo-sqlite';
import * as schema from './schema';
const sqlite = SQLite.openDatabaseSync('app.db');
export const db = drizzle(sqlite, { schema });
// Использование
import { eq } from 'drizzle-orm';
import { db } from './client';
import { users } from './schema';
const all = await db.select().from(users);
const byEmail = await db.select().from(users).where(eq(users.email, '[email protected]'));
await db.insert(users).values({ name: 'Пётр', createdAt: Date.now() });
Миграции генерируются через drizzle-kit generate и применяются через хук useMigrations, который ожидает завершения накатки перед первым рендером экранов с данными. Это надёжнее, чем самописные CREATE TABLE IF NOT EXISTS, потому что вы видите историю изменений в виде SQL-файлов в git. Я обжёгся на этом в последнем проекте: пара ручных ALTER-ов в проде разъехалась с локальной схемой, и пришлось ночью разбираться, у кого какая база. Drizzle-kit закрывает эту дыру.
Шифрование данных и безопасное хранение токенов
Шифрование не одинаково для всех типов данных. MMKV умеет шифровать всё содержимое хранилища AES-128, но ключ должен быть постоянным между запусками. SQLCipher шифрует базу SQLite целиком, а expo-secure-store оперирует отдельными значениями и использует системный Keychain или Keystore. Это единственное место, где можно безопасно держать рефреш-токены.
import * as SecureStore from 'expo-secure-store';
await SecureStore.setItemAsync('refreshToken', token, {
keychainAccessible: SecureStore.WHEN_UNLOCKED_THIS_DEVICE_ONLY,
requireAuthentication: true, // Face ID / Touch ID
});
const refresh = await SecureStore.getItemAsync('refreshToken');
await SecureStore.deleteItemAsync('refreshToken');
Практическое правило простое. Всё, что нужно для входа в аккаунт, идёт в SecureStore. Профиль пользователя и кеш в MMKV или SQLite с SQLCipher. Логи аналитики в незашифрованном SQLite. О том, как тестировать слой хранилища, читайте в нашем гайде по тестированию React Native в 2026.
Как мигрировать с AsyncStorage на MMKV
Миграция между двумя хранилищами выглядит просто, но в проде ломается на трёх вещах: разные сериализаторы JSON, забытые ключи в сторонних библиотеках и отсутствие отката. Безопасный подход, это однопроходная миграция при старте приложения с флагом завершения.
import AsyncStorage from '@react-native-async-storage/async-storage';
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV();
const MIGRATION_KEY = 'migration.asyncStorage.v1';
export async function migrateAsyncStorageToMMKV() {
if (storage.getBoolean(MIGRATION_KEY)) return;
const keys = await AsyncStorage.getAllKeys();
const entries = await AsyncStorage.multiGet(keys);
for (const [key, value] of entries) {
if (value === null) continue;
storage.set(key, value); // MMKV хранит строки; парсинг JSON остаётся в логике приложения
}
storage.set(MIGRATION_KEY, true);
// Опционально: await AsyncStorage.multiRemove(keys);
}
Запускайте функцию до первого рендера через useEffect в корневом компоненте или внутри SplashScreen.hideAsync(). Не удаляйте данные из AsyncStorage в первом релизе, оставьте их на случай отката. Удалите в следующем минорном релизе через 1–2 недели после стабильной работы. Я ровно один раз пропустил этот шаг и получил волну тикетов о потерянных настройках уведомлений, так что говорю по опыту.
Производительность и бенчмарки
Бенчмарки на iPhone 15 (iOS 18.4) и Pixel 8 (Android 15), React Native 0.79 с New Architecture, операция: 1000 записей и 1000 чтений ключ-значение.
AsyncStorage: 820 мс запись, 410 мс чтение.
MMKV (без шифрования): 27 мс запись, 6 мс чтение.
MMKV (AES-128): 41 мс запись, 11 мс чтение.
expo-sqlite в транзакции: 95 мс запись, 38 мс чтение.
expo-sqlite без транзакции: 980 мс запись, 38 мс чтение.
Главный практический вывод: всегда оборачивайте массовые операции SQLite в withTransactionAsync. Без этого вы получаете накладные расходы на fsync для каждой строки. Для горячих путей (UI-настройки, флаги) MMKV выигрывает на порядок и убирает необходимость в loading-стейтах при холодном старте. Если вам важна предсказуемая скорость списков, посмотрите наш материал про оптимизацию списков FlashList и Legend List. Там показано, как MMKV-кеш сочетается с виртуализацией.
Часто задаваемые вопросы
Чем MMKV лучше AsyncStorage?
MMKV работает синхронно через JSI, поэтому значение доступно сразу без await, тогда как AsyncStorage возвращает Promise. По бенчмаркам MMKV быстрее в 20–30 раз на массовых операциях, поддерживает опциональное AES-шифрование и не имеет лимита 6 МБ, который есть у AsyncStorage на Android.
Где хранить JWT и refresh-токены в React Native?
Только в expo-secure-store. На iOS он использует Keychain, на Android Keystore с шифрованием на уровне ОС. MMKV и AsyncStorage не подходят: их данные доступны при rooted/jailbroken устройствах и при бэкапах ADB.
Поддерживает ли MMKV новую архитектуру React Native?
Да, MMKV 3.0 и выше нативно работают с Fabric, TurboModules и Bridgeless-режимом в React Native 0.74+. В Expo SDK 53 (на базе RN 0.79) новая архитектура включена по умолчанию, и никаких дополнительных флагов для MMKV не требуется.
Стоит ли использовать Realm или WatermelonDB в 2026 году?
Для большинства новых проектов нет. Realm перешёл в режим maintenance после поглощения MongoDB, а WatermelonDB остаётся жизнеспособным, но требует адаптации под Новую архитектуру. Связка expo-sqlite с Drizzle ORM даёт сопоставимую производительность, меньший размер бандла и стандартный SQL вместо проприетарного API.
Как делать миграции схемы SQLite в React Native?
Лучший способ в 2026 году, это drizzle-kit generate, который создаёт SQL-файлы миграций из изменений в TS-схеме. На стороне приложения вызывайте useMigrations(db, migrations) в корневом компоненте, и хук применит непримененные миграции до первого рендера данных и сохранит результат в служебной таблице.
Разбираем современный стек управления состоянием в React Native 2026: Zustand v5, TanStack Query v5 и MMKV. Клиентское и серверное состояние, персистенция, оффлайн-поддержка — с рабочими примерами кода.