คู่มือจัดเก็บข้อมูล React Native 2026: MMKV V4, AsyncStorage, expo-sqlite เปรียบเทียบพร้อมโค้ด

เปรียบเทียบ MMKV V4, AsyncStorage และ expo-sqlite สำหรับ React Native 2026 พร้อม benchmark ความเร็ว โค้ดตัวอย่างที่ใช้ได้เลย และวิธีต่อ Zustand Persist Middleware

ทำไมการเลือก Local Storage ถึงสำคัญกว่าที่คิด

ถ้าคุณเคยสร้างแอป React Native มาสักพัก น่าจะเคยเจอปัญหานี้ — แอปเปิดมาแล้วต้องรอโหลดข้อมูลจากเครื่อง หน้าจอกระพริบขาวก่อนแสดงธีมที่ผู้ใช้ตั้งไว้ หรือ token หายหลังปิดแอป ปัญหาพวกนี้ล้วนเกี่ยวกับการเลือกวิธีจัดเก็บข้อมูลบนเครื่อง

ไม่ว่าจะเป็นการตั้งค่าผู้ใช้ โทเคนยืนยันตัวตน แคชข้อมูล หรือฐานข้อมูลออฟไลน์ การเลือกเครื่องมือจัดเก็บข้อมูลที่เหมาะสมส่งผลโดยตรงต่อทั้งความเร็ว ความปลอดภัย และ UX ของแอป

ในปี 2026 ตัวเลือกหลักสำหรับ React Native มีสามตัว:

  • AsyncStorage — ตัวเลือกพื้นฐานดั้งเดิมที่หลายคนเริ่มต้นด้วย
  • react-native-mmkv V4 — เร็วกว่า AsyncStorage ถึง 30 เท่า เพิ่งเขียนใหม่ด้วย Nitro Module
  • expo-sqlite — สำหรับข้อมูลเชิงสัมพันธ์ที่ซับซ้อนกว่า key-value

บทความนี้จะเปรียบเทียบทั้งสามตัวอย่างละเอียด พร้อมโค้ดที่ copy ไปใช้ได้เลย และวิธีต่อกับ Zustand Persist Middleware ด้วย

AsyncStorage — ตัวเลือกพื้นฐานที่ยังมีที่ยืน

AsyncStorage คืออะไร

AsyncStorage เป็นระบบจัดเก็บข้อมูลแบบ key-value ที่ไม่เข้ารหัส ทำงานแบบ asynchronous และเก็บข้อมูลถาวรแม้ปิดแอป เดิมทีเป็นส่วนหนึ่งของ React Native core แต่ตอนนี้แยกออกมาเป็น community package @react-native-async-storage/async-storage แล้ว

บน iOS จะใช้ native code ที่เก็บค่าขนาดเล็กใน serialized dictionary ส่วนค่าขนาดใหญ่เก็บในไฟล์แยก สำหรับ Android จะใช้ RocksDB หรือ SQLite แล้วแต่ว่าตัวไหนพร้อมใช้

การติดตั้งและใช้งาน

npm install @react-native-async-storage/async-storage
# หรือสำหรับ Expo
npx expo install @react-native-async-storage/async-storage

ตัวอย่างการใช้งานพื้นฐาน:

import AsyncStorage from '@react-native-async-storage/async-storage';

// บันทึกข้อมูล
const saveData = async () => {
  try {
    await AsyncStorage.setItem('user_theme', 'dark');
    await AsyncStorage.setItem('user_lang', 'th');
  } catch (e) {
    console.error('บันทึกข้อมูลล้มเหลว:', e);
  }
};

// อ่านข้อมูล
const loadData = async () => {
  try {
    const theme = await AsyncStorage.getItem('user_theme');
    if (theme !== null) {
      console.log('ธีมปัจจุบัน:', theme);
    }
  } catch (e) {
    console.error('อ่านข้อมูลล้มเหลว:', e);
  }
};

// ลบข้อมูล
const removeData = async () => {
  try {
    await AsyncStorage.removeItem('user_theme');
  } catch (e) {
    console.error('ลบข้อมูลล้มเหลว:', e);
  }
};

ข้อจำกัดที่ควรรู้

พูดตรงๆ เลยว่า AsyncStorage มีข้อจำกัดพอสมควร:

  • ความเร็ว: ทำงานผ่าน Bridge แบบ asynchronous ทำให้ช้ากว่าวิธีอื่นค่อนข้างมาก
  • ขนาดจำกัด: บน Android รองรับแค่ 6 MB (iOS ไม่จำกัด) ถ้าทำแอป cross-platform ต้องคิดว่า 6 MB คือเพดาน
  • ไม่มีการเข้ารหัส: อย่าเก็บรหัสผ่านหรือ token ตรงๆ เด็ดขาด
  • รองรับแค่ string: ข้อมูลที่ซับซ้อนต้อง JSON.stringify/parse เองทุกครั้ง ซึ่งก็... น่าเบื่อนิดหน่อย

react-native-mmkv V4 — Nitro Module ที่เร็วสุดในตอนนี้

MMKV คืออะไร

MMKV พัฒนาโดยทีม WeChat เป็นไลบรารีจัดเก็บข้อมูลแบบ key-value ที่ใช้ memory-mapped files ทำให้เข้าถึงข้อมูลจากหน่วยความจำโดยตรง ไม่ต้องอ่านจากดิสก์แบบ AsyncStorage

จุดเปลี่ยนสำคัญคือเวอร์ชัน 4 ที่ถูกเขียนใหม่ทั้งหมดเพื่อใช้ Nitro Module ทำให้โค้ดกระชับขึ้น เรียก native ได้เร็วขึ้น และที่หลายคนรอ — กลับมารองรับ Old Architecture อีกครั้งผ่าน Nitro

ข้อกำหนดเบื้องต้น

  • React Native 0.75.0 ขึ้นไป
  • react-native-nitro-modules เวอร์ชัน 0.35.0 ขึ้นไป
  • เวอร์ชันล่าสุดคือ MMKV 4.2.0

การติดตั้ง

# สำหรับ bare React Native
npm install react-native-mmkv react-native-nitro-modules
cd ios && pod install

# สำหรับ Expo
npx expo install react-native-mmkv react-native-nitro-modules
npx expo prebuild

การใช้งาน MMKV V4

สิ่งที่ต้องระวังใน V4 คือ class MMKV ไม่มีแล้ว ถ้าอัปเกรดจาก V3 ต้องเปลี่ยนมาใช้ฟังก์ชัน createMMKV() แทน:

import { createMMKV } from 'react-native-mmkv';

// สร้าง instance เดียวแล้ว export ใช้ทั่วทั้งแอป
export const storage = createMMKV();

// บันทึกข้อมูลหลายประเภท (synchronous ทั้งหมด!)
storage.set('user.name', 'สมชาย');
storage.set('user.age', 28);
storage.set('user.isPremium', true);

// อ่านข้อมูล
const name = storage.getString('user.name');     // 'สมชาย'
const age = storage.getNumber('user.age');        // 28
const isPremium = storage.getBoolean('user.isPremium'); // true

// ลบข้อมูล (V4 เปลี่ยนจาก delete เป็น remove นะ)
storage.remove('user.name');

// ตรวจสอบว่ามี key อยู่หรือไม่
const exists = storage.contains('user.age');      // true

// ดึง key ทั้งหมด
const allKeys = storage.getAllKeys();              // ['user.age', 'user.isPremium']

สังเกตไหมว่าไม่มี async/await เลย ทุกอย่าง synchronous หมด ซึ่งทำให้ใช้งานง่ายมากและไม่ต้องกังวลเรื่อง race condition

การเข้ารหัสข้อมูลด้วย MMKV

อีกจุดเด่นสำคัญคือ MMKV รองรับการเข้ารหัสข้อมูลในตัว ซึ่ง AsyncStorage ไม่มี:

import { createMMKV } from 'react-native-mmkv';

// สร้าง instance ที่เข้ารหัสด้วย encryption key
const secureStorage = createMMKV({
  id: 'secure-storage',
  encryptionKey: 'my-super-secret-key'
});

// เก็บ token อย่างปลอดภัย
secureStorage.set('auth.token', 'eyJhbGciOiJIUzI1NiIs...');
secureStorage.set('auth.refreshToken', 'dGhpcyBpcyBh...');

expo-sqlite — ฐานข้อมูลเชิงสัมพันธ์บนมือถือ

เมื่อไหร่ควรหยิบ SQLite มาใช้

ถ้าข้อมูลของคุณเริ่มมีความสัมพันธ์ที่ซับซ้อน เช่น one-to-many หรือ many-to-many หรือต้องเก็บข้อมูลหลายพันถึงหลายล้านแถว SQLite คือคำตอบ key-value storage อย่าง MMKV หรือ AsyncStorage จัดการพวกนี้ไม่ไหว

การติดตั้ง expo-sqlite

npx expo install expo-sqlite

ถ้าต้องการเปิด Full-Text Search (FTS) หรือ SQLCipher สำหรับเข้ารหัส ตั้งค่าเพิ่มใน app.config.js:

// app.config.js
export default {
  expo: {
    plugins: [
      [
        "expo-sqlite",
        {
          enableFTS: true,
          useSQLCipher: true
        }
      ]
    ]
  }
};

เปิดฐานข้อมูลด้วย Async API สมัยใหม่

ตั้งแต่ Expo SDK 51 เป็นต้นมา expo-sqlite มี Async API ใหม่ที่ใช้งานง่ายขึ้นเยอะ (เทียบกับ API เก่าที่ใช้ callback แล้วต่างกันมาก):

import * as SQLite from 'expo-sqlite';

// เปิดฐานข้อมูลและตั้งค่า
const initDatabase = async () => {
  const db = await SQLite.openDatabaseAsync('myapp.db');

  // เปิด WAL mode เพื่อประสิทธิภาพที่ดีขึ้น
  await db.execAsync('PRAGMA journal_mode = WAL;');
  // เปิดใช้ foreign keys
  await db.execAsync('PRAGMA foreign_keys = ON;');

  // สร้างตาราง
  await db.execAsync(`
    CREATE TABLE IF NOT EXISTS products (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      price REAL NOT NULL,
      category TEXT,
      created_at TEXT DEFAULT (datetime('now'))
    );
  `);

  return db;
};

ใช้ SQLiteProvider กับ React Context

expo-sqlite มี SQLiteProvider และ useSQLiteContext สำหรับแชร์การเชื่อมต่อฐานข้อมูลข้ามคอมโพเนนต์ ซึ่งสะดวกมากเพราะไม่ต้องส่ง db instance ผ่าน props:

import { SQLiteProvider, useSQLiteContext } from 'expo-sqlite';
import { Suspense } from 'react';

// ใน layout หลัก
export default function App() {
  return (
    <Suspense fallback={<LoadingScreen />}>
      <SQLiteProvider
        databaseName="myapp.db"
        onInit={migrateDatabase}
      >
        <MainApp />
      </SQLiteProvider>
    </Suspense>
  );
}

// ในคอมโพเนนต์ลูก
function ProductList() {
  const db = useSQLiteContext();
  const [products, setProducts] = useState([]);

  useEffect(() => {
    const loadProducts = async () => {
      const result = await db.getAllAsync(
        'SELECT * FROM products ORDER BY created_at DESC'
      );
      setProducts(result);
    };
    loadProducts();
  }, []);

  return (
    <FlatList
      data={products}
      renderItem={({ item }) => (
        <Text>{item.name} - ฿{item.price}</Text>
      )}
    />
  );
}

ตัวอย่าง CRUD จริงๆ

function useProductDB() {
  const db = useSQLiteContext();

  // เพิ่มสินค้า
  const addProduct = async (name, price, category) => {
    const result = await db.runAsync(
      'INSERT INTO products (name, price, category) VALUES (?, ?, ?)',
      [name, price, category]
    );
    return result.lastInsertRowId;
  };

  // ค้นหาสินค้าตามหมวดหมู่
  const getByCategory = async (category) => {
    return await db.getAllAsync(
      'SELECT * FROM products WHERE category = ? ORDER BY name',
      [category]
    );
  };

  // อัปเดตราคา
  const updatePrice = async (id, newPrice) => {
    await db.runAsync(
      'UPDATE products SET price = ? WHERE id = ?',
      [newPrice, id]
    );
  };

  // ลบสินค้า
  const deleteProduct = async (id) => {
    await db.runAsync('DELETE FROM products WHERE id = ?', [id]);
  };

  return { addProduct, getByCategory, updatePrice, deleteProduct };
}

Benchmark เปรียบเทียบประสิทธิภาพ

มาถึงส่วนที่หลายคนรอ — ตัวเลข benchmark จากการทดสอบ get operation 1,000 ครั้งในปี 2026:

รายการAsyncStorageMMKV V4expo-sqlite
ประเภทKey-ValueKey-ValueRelational DB
APIAsync/AwaitSynchronousAsync/Sync
ความเร็วอ่าน~2.5ms~0.5ms (เร็วกว่า 5 เท่า)~1.2ms
ความเร็วเขียน~2.9ms~0.6ms (เร็วกว่า 5 เท่า)~1.5ms
1,000 reads~242ms~12ms (เร็วกว่า 20 เท่า)~80ms
การเข้ารหัสไม่มีมีในตัวผ่าน SQLCipher
ขนาดจำกัด (Android)6 MBไม่จำกัดไม่จำกัด
Complex Queriesไม่รองรับไม่รองรับSQL เต็มรูปแบบ
Expo Supportมีต้อง prebuildมี (built-in)

ตัวเลขพูดเองชัดเจน — MMKV V4 เร็วกว่า AsyncStorage ถึง 20-30 เท่าในการทดสอบแบบ batch operation ถ้าแอปคุณอ่าน/เขียนข้อมูลบ่อย ความแตกต่างนี้ผู้ใช้สัมผัสได้จริงๆ

เชื่อมต่อ Zustand กับ MMKV ผ่าน Persist Middleware

ถ้าคุณใช้ Zustand จัดการ state อยู่แล้ว (หรือเคยอ่านบทความ คู่มือ Zustand สำหรับ React Native 2026 ของเรา) ขั้นตอนต่อไปคือทำให้ state เก็บถาวรด้วย MMKV ซึ่งทำได้ง่ายมากผ่าน persist middleware

วิธีที่ 1: สร้าง Storage Adapter เอง

import { createMMKV } from 'react-native-mmkv';
import { StateStorage } from 'zustand/middleware';

const mmkv = createMMKV();

// สร้าง adapter ที่เข้ากันกับ Zustand
export const zustandMMKVStorage: StateStorage = {
  setItem: (name, value) => {
    mmkv.set(name, value);
  },
  getItem: (name) => {
    const value = mmkv.getString(name);
    return value ?? null;
  },
  removeItem: (name) => {
    mmkv.remove(name);
  },
};

แค่นี้เอง ไม่กี่บรรทัดก็เสร็จ

วิธีที่ 2: ใช้ zustand-mmkv-storage

ถ้าไม่อยากเขียน adapter เอง ก็มี package สำเร็จรูป:

npm install zustand-mmkv-storage

สร้าง Store ที่เก็บข้อมูลถาวร

ตัวอย่างนี้เป็น cart store ที่ข้อมูลจะไม่หายแม้ปิดแอป:

import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { zustandMMKVStorage } from './storage';

interface CartState {
  items: Array<{ id: string; name: string; qty: number }>;
  addItem: (id: string, name: string) => void;
  removeItem: (id: string) => void;
  clearCart: () => void;
}

export const useCartStore = create<CartState>()(
  persist(
    (set) => ({
      items: [],
      addItem: (id, name) =>
        set((state) => {
          const existing = state.items.find((i) => i.id === id);
          if (existing) {
            return {
              items: state.items.map((i) =>
                i.id === id ? { ...i, qty: i.qty + 1 } : i
              ),
            };
          }
          return { items: [...state.items, { id, name, qty: 1 }] };
        }),
      removeItem: (id) =>
        set((state) => ({
          items: state.items.filter((i) => i.id !== id),
        })),
      clearCart: () => set({ items: [] }),
    }),
    {
      name: 'cart-storage',
      storage: createJSONStorage(() => zustandMMKVStorage),
    }
  )
);

พอผู้ใช้เปิดแอปใหม่ สินค้าในตะกร้ายังอยู่ครบเพราะ MMKV เก็บไว้บนดิสก์ให้แล้ว และเนื่องจาก MMKV เป็น synchronous จึงไม่มีปัญหา "flash of initial state" ที่มักเกิดจากการรอ hydration แบบ async (ปัญหานี้น่ารำคาญมากถ้าเคยเจอ)

จัดการ Hydration ให้ถูกต้อง

ถึงแม้ MMKV จะเป็น synchronous แต่ Zustand persist middleware ยังทำงานแบบ async อยู่ ดังนั้นควรเช็ค hydration status ก่อนแสดง UI:

import { useCartStore } from './stores/cartStore';

function App() {
  // ตรวจสอบว่า store ดึงข้อมูลจาก MMKV เสร็จแล้วหรือยัง
  const hasHydrated = useCartStore(
    (state) => state._hasHydrated
  );

  if (!hasHydrated) {
    return <SplashScreen />;
  }

  return <MainApp />;
}

แนวทางเลือกใช้งานตาม Use Case

เอาล่ะ มาสรุปกันว่าแต่ละตัวเหมาะกับงานแบบไหน

ใช้ AsyncStorage เมื่อ

  • เก็บข้อมูลตั้งค่าง่ายๆ ที่ไม่ซีเรียสเรื่องความเร็ว
  • โปรเจกต์เล็กที่ไม่อยากเพิ่ม dependency
  • ต้องการความเข้ากันได้กับ Expo Go โดยไม่ต้อง prebuild

ใช้ MMKV V4 เมื่อ

  • ต้องการความเร็วสูงสุดในการอ่าน/เขียน key-value
  • เก็บ token, session, ค่า config, หรือ state ของ Zustand/Redux
  • ต้องการเข้ารหัสข้อมูลในตัว
  • ต้องการ synchronous API ที่ไม่ต้อง async/await
  • แอปที่ performance สำคัญ อย่างแอป fintech หรือ e-commerce

ใช้ expo-sqlite เมื่อ

  • ข้อมูลมีความสัมพันธ์ซับซ้อน (สินค้า-หมวดหมู่-คำสั่งซื้อ)
  • ต้อง query ด้วยเงื่อนไขหลายตัว, sort, join
  • จัดเก็บข้อมูลหลายพันถึงหลายล้านแถว
  • ต้องการ full-text search
  • สร้างแอปที่ทำงานออฟไลน์เต็มรูปแบบ

ใช้ผสมกัน (แนะนำสำหรับแอปจริงๆ)

จากประสบการณ์ แอป production ส่วนใหญ่ใช้มากกว่าหนึ่งตัวเลือก ตัวอย่างการจับคู่ที่ใช้บ่อย:

  • MMKV — สำหรับ token, การตั้งค่า, state ของ Zustand
  • expo-sqlite — สำหรับข้อมูลสินค้า, ประวัติคำสั่งซื้อ, แคชข้อมูล API
  • AsyncStorage — เอาไว้ใช้กับ library เก่าบางตัวที่ยังต้องการมันเป็น dependency

เคล็ดลับสำหรับ Production

1. อย่าเก็บข้อมูลใหญ่ใน MMKV

เนื่องจาก MMKV ใช้ memory-mapped files ข้อมูลขนาดใหญ่จะกินหน่วยความจำโดยตรง ควรเก็บเฉพาะ key-value ขนาดเล็ก ข้อมูลใหญ่ๆ ให้ย้ายไป SQLite

2. เปิด WAL Mode กับ SQLite เสมอ

PRAGMA journal_mode = WAL ช่วยให้ SQLite อ่านและเขียนพร้อมกันได้ ประสิทธิภาพต่างกันเยอะมากในแอปที่อ่านข้อมูลบ่อย ถือว่าเป็น best practice ที่ควรทำทุกโปรเจกต์

3. วางแผน Migration ให้ดี

เมื่อเปลี่ยนโครงสร้าง schema ของ SQLite ต้องมี migration strategy ที่ชัดเจน expo-sqlite รองรับ onInit callback ที่ช่วยจัดการ migration ตอนเปิดแอปครั้งแรกหลังอัปเดต ใช้มันให้เป็นประโยชน์

4. ทดสอบบนอุปกรณ์จริง

อันนี้สำคัญ — ค่า benchmark อาจต่างกันมากระหว่าง simulator กับอุปกรณ์จริง โดยเฉพาะ MMKV ที่ใช้ mmap จะทำงานได้ดีกว่ามากบนเครื่องจริง อย่าดูแค่ตัวเลขจาก simulator แล้วตัดสินใจ

คำถามที่พบบ่อย

MMKV กับ AsyncStorage อันไหนดีกว่าสำหรับเก็บ token?

MMKV ดีกว่าอย่างไม่ต้องสงสัย เพราะรองรับการเข้ารหัสข้อมูลในตัว และเป็น synchronous ทำให้อ่าน token ได้ทันทีตอนเปิดแอป ไม่ต้องรอ await ส่วน AsyncStorage ไม่มีการเข้ารหัส จึงไม่เหมาะกับข้อมูลที่เป็นความลับเลย

expo-sqlite ใช้ร่วมกับ Expo Go ได้ไหม?

ได้เลย expo-sqlite เป็นส่วนหนึ่งของ Expo SDK จึงใช้ได้ใน Expo Go โดยตรง แต่ถ้าต้องการเปิด SQLCipher หรือ FTS ผ่าน config plugin จะต้อง prebuild เป็น development build ก่อน

ควรเปลี่ยนจาก AsyncStorage ไป MMKV เลยทั้งหมดไหม?

สำหรับแอปใหม่ในปี 2026 แนะนำให้ใช้ MMKV เป็นค่าเริ่มต้นเลย ยกเว้นกรณีที่ต้องทำงานใน Expo Go โดยไม่ prebuild (เพราะ MMKV ต้องการ native module) อย่างไรก็ตาม ถ้า library อื่นที่คุณใช้อยู่ต้องการ AsyncStorage เป็น dependency ก็ยังจำเป็นต้องติดตั้งเอาไว้

เก็บรูปภาพใน SQLite ได้ไหม?

ทำได้ทางเทคนิค (เก็บเป็น BLOB) แต่ไม่แนะนำเลย ควรเก็บรูปภาพเป็นไฟล์บนเครื่อง (ใช้ expo-file-system) แล้วเก็บแค่ path ในฐานข้อมูล จะประหยัดหน่วยความจำและประสิทธิภาพดีกว่าเยอะ

react-native-mmkv V4 รองรับ Old Architecture ไหม?

รองรับ นี่คือหนึ่งในข้อดีหลักของ V4 เลย — กลับมารองรับ Old Architecture อีกครั้งผ่าน Nitro Module ทำให้ไม่จำเป็นต้องเปิด New Architecture ก็ใช้งานได้ แต่ถ้าพร้อมแนะนำให้อัปเกรดเป็น New Architecture อยู่ดี เพราะจะได้ performance ที่ดีกว่า

เกี่ยวกับผู้เขียน Editorial Team

Our team of expert writers and editors.