React로 IndexedDB를 활용한 오프라인 데이터 저장
React와 IndexedDB를 결합해 네트워크 연결 없이도 앱 데이터의 지속성을 확보하는 오프라인 저장소 구축 과정과 localForage 연동 예제, 성능 및 동기화 고려사항 설명
목차
왜 IndexedDB인가
IndexedDB는 브라우저 내장 비관계형 저장소다. 로컬 스토리지보다 용량이 크고, 구조화된 데이터와 인덱스를 다룰 수 있다. 오프라인 상태에서도 데이터 지속성이 필요할 때 적합하다. React 앱에선 상태 관리와 IndexedDB를 함께 쓰면 네트워크 장애에도 사용자 경험을 유지할 수 있다.
기본 개념 정리
데이터 모델
IndexedDB는 데이터베이스, 오브젝트 스토어(object store), 트랜잭션, 인덱스로 구성된다. 오프라인 저장소 react 설계 시 데이터 파편화를 줄이고 인덱스를 충분히 설계하는 것이 중요하다.
localForage 사용 이점
localForage는 IndexedDB를 추상화하여 사용을 간편하게 한다. 비동기 API와 Promise 기반 접근을 제공하므로 React와 자연스럽게 연동된다. 브라우저 호환성, 자동 드라이버 선택, 직렬화 처리를 덜어준다.
설정과 초기화
프로젝트에 localForage를 설치한다. 그런 다음 기본 설정을 한다. 아래 예제는 localForage를 IndexedDB 우선으로 설정하는 방법이다.
import localforage from 'localforage';
localforage.config({
name: 'myApp',
storeName: 'keyval', // object store 이름
driver: [localforage.INDEXEDDB, localforage.WEBSQL, localforage.LOCALSTORAGE]
});
export default localforage;
React와 연동하는 패턴
React에서는 직접 DOM 접근을 피하고 상태 기반으로 구성한다. IndexedDB 접근은 비동기이므로 훅을 만들어 재사용한다. 아래는 간단한 useLocalDB 훅 예제다.
import { useState, useEffect, useCallback } from 'react';
import localforage from './localforage-config';
export function useLocalDB(key, initialValue = null) {
const [value, setValue] = useState(initialValue);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
let mounted = true;
localforage.getItem(key)
.then((v) => {
if (mounted) setValue(v ?? initialValue);
})
.catch((e) => { if (mounted) setError(e); })
.finally(() => { if (mounted) setLoading(false); });
return () => { mounted = false; };
}, [key, initialValue]);
const save = useCallback((v) => {
setValue(v);
return localforage.setItem(key, v);
}, [key]);
const remove = useCallback(() => {
setValue(null);
return localforage.removeItem(key);
}, [key]);
return { value, loading, error, save, remove };
}
React 컴포넌트에서 사용 예
간단한 메모 앱 컴포넌트 예제다. 이 예제는 오프라인 저장소 react 구조에서 입력을 로컬에 보존한다.
import React from 'react';
import { useLocalDB } from './useLocalDB';
export default function NoteEditor() {
const { value: note, loading, save } = useLocalDB('note', '');
if (loading) return <div>로딩 중...</div>;
return (
<div>
<h3>메모</h3>
<textarea
value={note}
onChange={(e) => save(e.target.value)}
rows={8}
cols={40}
/>
</div>
);
}
동기화 전략
오프라인 데이터를 서버와 동기화하려면 충돌 해결과 큐잉 전략이 필요하다. 일반적인 흐름은 다음과 같다.
- 작업 큐에 변경을 기록한다.
- 네트워크 복구 시 서버에 순차적으로 전송한다.
- 서버 응답을 받아 성공한 항목은 큐에서 제거한다.
- 충돌 발생 시 타임스탬프나 버전 기반 병합을 적용한다.
이 과정은 오프라인 저장소 react 구현에서 핵심이다. 동기화 로직을 잘 설계하면 데이터 일관성을 높일 수 있다.
성능과 용량 고려사항
IndexedDB는 대용량 저장에 적합하지만 무분별한 저장은 성능 저하를 유발한다. 아래를 고려한다.
- 불필요한 중복 저장을 피한다.
- 큰 파일(이미지, 바이너리)은 Blob으로 관리한다.
- 인덱스를 통해 조회 비용을 줄인다.
- 정기적인 정리(cleanup) 전략을 적용한다.
테스트와 디버깅
브라우저 개발자 도구의 Application 탭에서 IndexedDB를 확인한다. localForage가 내부적으로 어떤 드라이버를 사용하는지 로그로 남기면 문제 원인 파악에 도움이 된다.
권장 패턴 요약
- localForage로 브라우저 저장소 추상화
- React 훅으로 비동기 접근 캡슐화
- 변경 큐와 동기화 작업 분리
- 충돌 해결 정책(타임스탬프·버전) 명확화
- 성능을 위한 인덱스와 정리 전략 적용
결론
이 글에서는 react indexeddb 사용법을 중심으로 오프라인 저장소 react 설계와 localforage react 연동 예제를 다루었다. IndexedDB는 오프라인 퍼시스턴스를 구현하는 강력한 도구다. localForage와 결합하면 구현 난이도가 낮아지고 유지보수가 쉬워진다. 성능과 동기화 정책을 고려해 구조를 설계하면 실제 사용자 환경에서 안정적인 오프라인 경험을 제공할 수 있다.