React Native MMKV

eeensu·2026년 3월 8일

React Native

목록 보기
19/35

RN의 로컬스토리지인 MMKV

MMKV는 현재 React Native 생태계에서 로컬 데이터 저장소(Local Storage)의 사실상 표준(De-facto Standard)이다.

과거의 AsyncStorage를 완전히 대체하는 라이브러리로, 빠른 속도와 동기(Synchronous) 처리가 핵심이다. WeChat(Tencent)이 개발한 네이티브 라이브러리를 Marc Rousavy가 React Native용으로 포팅(react-native-mmkv)하여 개발했다.


1. 왜 빠른가?

MMKV는 AsyncStorage보다 50배 정도 더 빠르다. 그 이유는 두 가지 핵심 기술 때문이다.

  • JSI (Javascript Interface)
    • AsyncStorage 의 방식 : JS와 네이티브가 데이터를 주고받을 때 JSON 직렬화 과정을 거치고, 비동기 브릿지를 건너야 했다. 느리고 무거웠다.
    • MMKV의 방식 : C++로 작성된 함수를 JS에서 직접 호출한다. 브릿지를 타지 않고, 직렬화 과정도 없다. JS 변수를 C++ 메모리 공간에 바로 꽂아 넣는 방식이다.
  • mmap (Memory Mapping)
    • 원리 : 운영체제(OS)의 가상 메모리 관리 기술인 mmap을 사용한다.
    • 효과 : 파일 입출력(I/O)을 메모리 읽기/쓰기처럼 처리한다.
    • 데이터를 쓰면 메모리에 먼저 기록되고, OS가 알아서 적절한 타이밍에 디스크(Flash Storage)로 동기화한다.
    • 충돌 방지 : 앱이 갑자기 죽어도(Crash), OS가 메모리 맵을 관리하므로 데이터 손실 확률이 극히 낮다.

MMKV는 JSI를 기반으로 동작하므로 RN 0.76+ 이상의 최신버전에선 New Architecture와 완벽하게 호환된다. 오히려 New Architecture가 기본 활성화되면서 MMKV의 성능 이점이 더욱 부각되고 있다. 구 아키텍처의 Bridge 방식보다 훨씬 빠르다.


2. 동기식 처리

기존 AsyncStorage의 가장 큰 단점은 모든 게 Promise(await)였다는 점이다.

// AsyncStorage (구형) - 비동기라 await 필수, 깜빡임 발생 가능
const loadUser = async () => {
  const user = await AsyncStorage.getItem('user');
  setUser(user);
};

하지만 MMKV는 동기식이다. 일반 변수처럼 바로 값을 가져온다.

// MMKV (신형) - 동기식이라 앱 켜자마자 데이터가 있음
const user = storage.getString('user');
// 바로 사용 가능 (로그인 상태 유지 등에 필수)
if (user) navigate('Home');

이 덕분에 앱 실행 시 로딩 스피너를 보여줄 필요 없이 즉시 저장된 상태를 복원할 수 있다. Zustand, Jotai 같은 상태 관리 라이브러리와 궁합이 좋다.


3. 기본 사용법

1) CRUD

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

// 1. 인스턴스 생성 (앱 전체에서 공유됨)
export const storage = new MMKV();

// 2. 저장 (Set) - 문자열, 숫자, 불리언 지원
storage.set('user.name', 'Marc');
storage.set('user.age', 21);
storage.set('is-dark-mode', true);

// 3. 읽기 (Get)
const username = storage.getString('user.name'); // 'Marc' or undefined
const age = storage.getNumber('user.age'); // 21
const isDark = storage.getBoolean('is-dark-mode'); // true

// 4. 삭제 (Delete)
storage.delete('user.name');

// 5. 전체 삭제
storage.clearAll();

2) Object
MMKV는 기본적으로 원시 타입(String, Number, Boolean)만 지원한다. 객체를 저장하려면 JSON.stringify를 써야 한다.

const user = { id: 1, role: 'admin' };
storage.set('user', JSON.stringify(user));

const savedUser = JSON.parse(storage.getString('user'));

3) 암호화
보안이 중요한 데이터(토큰 등)를 저장할 때 사용한다. MMKV는 기본적으로 default 라는 이름의 저장소를 하나 만들어 둔다. 하지만 이렇게 ID를 따로 주면 별도의 공간을 따로 만들어서 관리하겠다는 뜻이다. 다른 라이브러리나 앱의 다른 부분에서 MMKV를 쓰더라도, 데이터가 섞이지 않고 격리된다.

const storage = new MMKV({
  id: 'secure-storage',
  encryptionKey: 'my-secret-key' // 이 키를 사용해 암호화를 수행한다. 만약 누군가 루팅된 폰에서 이 앱의 데이터 파일을 탈취하더라도, 내용은 알 수 없는 암호문으로 보여 데이터를 볼 수 없다.
});

3) 다중 인스턴스
데이터를 격리해서 저장하고 싶을 때 (예: 사용자별 데이터 분리), 별도의 저장소를 만들 수 있다.

const user1Storage = new MMKV({ id: 'user-1' });
const user2Storage = new MMKV({ id: 'user-2' });

4) 리스너
데이터가 변경되었을 때 이벤트를 받을 수 있다. (React 밖에서 상태 변화 감지 시 유용)

const listener = storage.addOnValueChangedListener((key) => {
  console.log(`${key}가 변경되었습니다!`);
});

5) 리스너

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

function App() {
  const [name, setName] = useMMKVString('user.name');

  return <TextInput value={name} onChangeText={setName} />;
}

6) Zunstand 에서의 사용

import { create } from 'zustand';
import { createJSONStorage, persist } from 'zustand/middleware';
import { MMKV } from 'react-native-mmkv';

const storage = new MMKV();

// Zustand store를 MMKV에 자동으로 동기화
export const MMKV_ZUSTAND_STORAGE = {
  setItem: (name, value) => {
    return storage.set(name, value);
  },
  getItem: (name) => {
    const value = storage.getString(name);
    return value ?? null;
  },
  removeItem: (name) => {
    return storage.delete(name);
  },
};

const useStore = create(
  persist(
    (set) => ({
      token: null,
      setToken: (token) => set({ token }),
    }),
    {
      name: 'auth-storage',
      storage: createJSONStorage(() => MMKV_ZUSTAND_STORAGE),
    }
  )
);

4. 주의사항

  • 용량 제한

    • MMKV는 설정 값, 유저 세션, 작은 캐시 데이터를 저장하기 위해 설계되었다.
    • 이미지 파일이나 동영상 같은 대용량 바이너리 데이터를 저장하면 메모리 효율이 급격히 떨어진다. 그런 건 react-native-fs를 써서 파일 시스템에 저장해야 한다.
  • 동기 처리의 양면성

    • 너무 거대한 문자열(수 MB 이상)을 JSON.parse 하거나 읽어오면, 동기 방식이라 JS 스레드가 순간적으로 차단(Blocking)될 수 있다. (물론 웬만해선 AsyncStorage보다 빠르다.)
profile
안녕하세요! 프론트엔드 개발자입니다! (2024/03 ~)

0개의 댓글