20210427

react native asyncstorage

React Native AsyncStorage 사용법

AsyncStorage 는 간단히 말해 "앱이 꺼져도 남는 key-value 저장소" 다. 브라우저의 localStorage 와 개념이 비슷하고, 실제 플랫폼 구현은 iOS 쪽은 파일/SQLite, Android 쪽은 SQLite/SharedPreferences 로 알려져 있다.

2020 년에 core 에서 빠지고 @react-native-async-storage/async-storage 로 community 패키지로 이관됐다. 예전 글 보고 react-native 직접 import 시도하다가 deprecated 경고 보는 게 요즘 가장 흔한 케이스. RN 0.60 이후 auto-linking 덕분에 설치만 하면 되지만, iOS 는 pod install 한 번 돌려줘야 한다.

왜 AsyncStorage 인가

컴포넌트 state 는 언마운트되면 사라지고, redux 도 앱 종료되면 날아간다. 로그인 토큰, 튜토리얼 완료 여부, 마지막 본 화면 같이 세션 간에 살아남아야 하는 작은 값에는 AsyncStorage 가 딱이다. 반대로 복잡한 관계형 데이터, 큰 리스트, 풀텍스트 검색이 필요하면 SQLite (react-native-sqlite-storage) 또는 MMKV (react-native-mmkv) 같은 대안을 써야 한다. 특히 MMKV 는 성능이 AsyncStorage 보다 훨씬 좋다는 벤치가 많아서 최근 마이그레이션 이야기가 자주 나옴.

설치

$ npm install @react-native-async-storage/async-storage
# 또는
$ yarn add @react-native-async-storage/async-storage

# iOS
$ cd ios && pod install

RN 0.64 기준. 0.60 이상이면 따로 react-native link 안 해도 auto-linking 이 됨.

기본 사용 — 저장 / 읽기

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

// 저장 — Promise 기반
const saveId = async (id) => {
  try {
    await AsyncStorage.setItem('userId', id);
  } catch (e) {
    console.warn('save failed', e);
  }
};

// 읽기
const loadId = async () => {
  try {
    const id = await AsyncStorage.getItem('userId');
    return id;  // 없으면 null
  } catch (e) {
    return null;
  }
};

핵심 포인트 두 가지:

1. 모든 API 가 비동기 (Promise) 다. callback 스타일도 지원하지만 요즘은 async/await 가 기본.

2. 저장 가능한 값은 문자열뿐. 숫자, 객체를 넣으려면 stringify 해야 한다.

JSON 저장

const saveUser = async (user) => {
  await AsyncStorage.setItem('user', JSON.stringify(user));
};

const loadUser = async () => {
  const raw = await AsyncStorage.getItem('user');
  return raw ? JSON.parse(raw) : null;
};

이걸 한 프로젝트에서 여기저기 흩뿌려놓으면 key 네이밍이 뒤죽박죽 된다. 작은 wrapper 하나 만들어 두는 게 정신 건강에 좋다.

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

export const storage = {
  async get(key, fallback = null) {
    const raw = await AsyncStorage.getItem(key);
    if (raw == null) return fallback;
    try { return JSON.parse(raw); } catch { return raw; }
  },
  async set(key, value) {
    const raw = typeof value === 'string' ? value : JSON.stringify(value);
    await AsyncStorage.setItem(key, raw);
  },
  async remove(key) {
    await AsyncStorage.removeItem(key);
  },
};

여러 개 한번에 (multiGet / multiSet)

await AsyncStorage.multiSet([
  ['appVersion', '1.0.3'],
  ['lastLogin', String(Date.now())],
]);

const pairs = await AsyncStorage.multiGet(['appVersion', 'lastLogin']);
// [['appVersion', '1.0.3'], ['lastLogin', '1619...']]

루프 돌면서 setItem 반복 호출하지 말고 multiSet 으로 한번에 처리하면 훨씬 빠르다. 바쁘게 값 수십 개 저장할 일 있으면 체감 차이 크다.

머지 / 삭제 / 전체 초기화

// 기존 JSON 에 병합 (shallow merge)
await AsyncStorage.mergeItem('user', JSON.stringify({ lastSeen: Date.now() }));

// 단일 키 삭제
await AsyncStorage.removeItem('userId');

// 전부 날리기 — 로그아웃 처리에 자주 씀. 복구 불가.
await AsyncStorage.clear();

주의할 점

1. 암호화 안 됨. AsyncStorage 에 토큰을 그냥 평문으로 넣으면 root/jailbreak 된 디바이스에서는 들여다 볼 수 있다. 민감한 정보 (access token, refresh token, 결제정보 일부) 는 react-native-keychain 이나 expo-secure-store 쪽을 쓰자.

2. 용량 제한. Android 기본 빌드에서 AsyncStorage 전체 용량이 6MB 로 제한된다. 넉넉하게 쓰려면 앱 쪽에서 크기를 늘리는 설정을 해야 한다. 이걸 몰라서 "저장은 되는데 어느 순간부터 안 됨" 버그를 몇 시간 잡은 적이 있음.

// android/gradle.properties
AsyncStorage_db_size_in_MB=10

3. 성능. 매 프레임 setItem 을 호출하면 끊긴다. throttle / debounce 해서 일정 간격으로 저장하는 게 안전. 스크롤 위치 저장 같은 고빈도 업데이트는 특히 조심.

4. 앱 삭제 시 같이 지워짐. 앱 삭제 → 재설치 하면 데이터 날아간다. "앱 재설치해도 로그인 상태 유지" 하려면 Keychain (iOS) / AccountManager (Android) 같은 시스템 저장소가 필요.

참고 문서

정리하면, AsyncStorage 는 "가볍고 비동기인 key-value 저장소" 로 딱 맞는 위치에만 쓰면 편하다. 민감 정보는 안 넣고, 큰 데이터는 안 넣고, 반복 쓰기는 throttle 건다. 이 세 가지 만 지키면 대부분 문제 없음.