본문 바로가기
FE/browser

[browser] IndexedDB

by livemehere 2023. 10. 7.

브라우저에 내장되어있는 저장소의 대표적인 종류는 localStorage, sessionStorage, cookie 가 있습니다.

하지만 모두 적은 용량의 데이터를 저장할 수 있는데, IndexedDB 는 저장공간이 OS 의 하드디스크 용량의 일정량을 저장할 수 있기 때문에 실제 DB를 사용하는 것 만큼의 양을 저장할 수 있습니다.

정확한 용량은 다음 코드로 총사용량과 총 사용가능 용량을 계산할 수 있습니다.

const quota = await navigator.storage.estimate();
const percentageUsed = (quota.usage / quota.quota) * 100;
const remaining = (quota.quota - quota.usage) / 1024 / 1024;

 

IndexedDB

indexedDB 는 다음과 같은 특징이 있습니다.

- 단일 키 혹은 다중 키로 값을 저장할 수 있습니다.

- transaction 을 지원합니다.

- 인덱싱과 인덱싱한 필드에 대한 쿼리 조회를 지원합니다.

 

기본 API를 사용하기엔 callback 형태라 코드가 꽤많이 복잡해집니다.

그래서 promise 베이스의 wrapper 를 추천해주고 있습니다.

하지만 API 가 몇개 없어서 직접 promise 로 감싸면서 사용해도 충분합니다.

 

 

DB 생성하기

스키마를 생성/삭제/업데이트 하는 것은 반드시 onupgradeneeded 이벤트 콜백 내에서만 가능합니다.

버전을 관리하면서 onupgradeneeded 를 호출하도록하여 스키마를 수정하여야 합니다.

의도적으로 dispatchEvent() 를 발생시킬 수 도 있습니다.

 

스키마의 정식 명칭은 "ObjectStore" 입니다.
let openRequest = indexedDB.open("DB이름", 버전); // 크롬기준 최신버전은 2입니다.
let db;

openRequest.onupgradeneeded = function() {
  if (!db.objectStoreNames.contains("스키마이름")) { // 스키마가 존재하지 않는다면
    db.createObjectStore("스키마이름", {keyPath: 'id'}); // 새로운 스키마 생성
  }
};

openRequest.onerror = function() {
  console.error("Error", openRequest.error);
};

openRequest.onsuccess = function() {
	db = openRequest.result;
};

스키마를 생성할때 옵션값으로 keyPath 를 넘겨주어, 값에서 어떤 프로퍼티를 key 로 사용할 것인지 명시합니다.

 

 

Transactions

IndexedDB 의 모든 CRUD 는 트랜젝션 내에서 수행합니다.

조회하는 API 는 예시에서 사용한 것 외에 조금 더있습니다.

 

단 주의할 점은 db.transaction(스키마이름, 모드) 에서 조회시에는 "readonly", add,put 동작시에는 "readwrite" 를 넣어줍니다.

const transaction = db.transaction("스키마 이름", "readwrite"); // 트랜잭션 생성
const store = transaction.objectStore("스키마 이름"); // 스키마 가져오기

const todo = {
  id: Date.now(),
  title:'todo1',
};

const request = store.add(todo); // 데이터 Add
// const request = store.put(todo); // Add 대신 Put 은 데이터가 있으면 덮어씌우고, 없다면 Add 합니다.
// const request = store.delete(key); // key 값으로 데이터를 지웁니다.
// const request = store.clear(); // 스키마의 모든 데이터를 지웁니다.

// const request = store.getAll(); // 스키마의 모든 데이터를 가져옵니다.
// const request = store.get(key); // key 값에 해당하는 데이터 한개를 가져옵니다.
// const request = store.delete(key) // key 값에 해당하는 데이터 제거

request.onsuccess = function() { // 성공 결과 callback
  console.log("Book added to the store", request.result); 
};

request.onerror = function() { // 실패 결과 callback
  console.log("Error", request.error);
};

 

인덱싱으로 조회하기

인덱싱은 스키마를 생성할 때 추가해줘야 합니다.

openRequest.onupgradeneeded = function() {
  if (!db.objectStoreNames.contains("스키마이름")) { // 스키마가 존재하지 않는다면
    const store = db.createObjectStore("스키마이름", {keyPath: 'id'}); // 새로운 스키마 생성
    store.createIndex('title_idx','title') // 데이터의 title 이라는 프로퍼티로 인덱싱
  }
};
const transaction = db.transaction("스키마 이름", "readwrite"); // 트랜잭션 생성
const store = transaction.objectStore("스키마 이름"); // 스키마 가져오기
const index = store.index("title_idx") // 인덱싱한 이름

const request = index.getAll("값") // 찾을 인덱싱한 프로퍼티의 값

request.onsuccess = function() { // 성공 결과 callback
  console.log("Book added to the store", request.result); 
};

request.onerror = function() { // 실패 결과 callback
  console.log("Error", request.error);
};

 

Promise 로 감싸기

IndexedDB 의 모든 CRUD 가

(1) 트랜잭션 생성

(2) 스키마 가져오기

(3) CRUD

순서입니다.

 

모두 비슷한 맥락으로 아래와 같이 간단히 Promise 를 반환하도록 작성해 사용할 수 있습니다.

  const findById = <T extends IDBItem>(
    storeName: string,
    key: string | number,
  ) => {
    const transaction = db?.transaction(storeName, "readonly");
    const store = transaction?.objectStore(storeName);
    return new Promise<T>((resolve, reject) => {
      const req = store.get(key);
      req.onsuccess = () => {
        resolve(req.result);
      };

      req.onerror = () => {
        reject(req.error);
      };
    });
  };

 

마무리

공부하면서 느낀것은 용량이 OS 에 종속되어, 오프라인 서비스로 한다면 거의 제한없이 사용할 수 있을만한 메리트를 가지고 있는거 같습니다. 하지만 디비라고 하기엔 쿼리가 단순히 하나의 프로퍼티를 조회하는 정도에 그치고, 포스팅에서 다루진 않았지만, "IDBKeyRange" 를 가지고 범위로 쿼리를 할 수 있긴합니다만.. 숫자외에는 복잡한 쿼리를 사용하긴 무리가 있어보입니다.

그래도 적절한 상황에서 사용할 하나의 무기가 있다는 정도? 의 안정감이 느껴지는 듯 합니다. 😄

반응형

'FE > browser' 카테고리의 다른 글

[browser] 브라우저의 Storage  (0) 2023.10.06
[browser] Cookie  (0) 2023.10.06
[browser] Blob 다루기  (0) 2023.10.05
[Browser] Service Worker 로 push notification 구현하기  (0) 2023.09.21
[Browser] Web Worker 와 Service Worker  (0) 2023.09.20