React+TS 환경에서 IndexedDB 적용하기(1)

도입계기
사내 프로젝트의 2차 릴리즈를 준비하는 과정에서 iframe 간 통신의 요구사항이 고도화되며 기존 방식인 localStorage만으로 100% 구현할 수 없는 상황이 되었다.
localStorage의 특성상 문자열만을 저장할 수 있기 때문에, json 형식의 데이터나 콜백 함수 등 요구사항을 모두 구현하는데 불편함이 있었다. (2편에 함수를 넣지 못하는 과정이 추가되었습니다)
이를 해결하기 위한 대안으로 indexedDB를 적용하게 되었고, 학습 내용과 적용 과정을 기록해보았다.
IndexedDB 개념
이미지 출처 - 생활코딩
IndexedDB란 브라우저에서 제공하는 데이터베이스이다.
Cookie와 LocalStorage와는 다르게 대용량의 데이터를 저장할 수 있고, 다양한 데이터 타입을 기록할 수 있기 때문에 다양하게 활용할 수 있다.
뿐만 아니라 비동기식으로 동작하기 때문에 다른 방식들에 비해 뛰어난 성능까지 기대할 수 있다.
IndexedDB의 구조
IndexedDB는 다음과 같은 구조를 가진 Database이다.
이미지 출처 - 생활코딩
이러한 구조를 갖기 때문에, 사용자가 원하는 Object를 만들어서 저장하기 위해서는
- Database를 생성한뒤,
- transaction을 통해 내가 사용할 Object의 묶음인 ObjectStore을 Database에 생성해주고,
- 필요한 Object를 만들어 사용하면 된다!
하위 내용은 생활코딩 강의와 MDN 문서를 보며 react + typescript 환경에 맞게 실습한 간단한 예시와 프로젝트에 적용한 코드의 일부이다.
IndexedDB 생성
1. DataBase 생성
// Open IndexedDB
const request = indexedDB.open("userDB", 1);
let db: IDBDatabase;
request.onerror = () => {
alert("IndexedDB Open Error!");
};
request.onsuccess = () => {
db = request.result;
};
indexedDB에 database를 생성할 때 name과 version 2개의 인자가 필요하다.
(name: string, version?: number | undefined)
name은 말그대로 database의 이름이고, version은 해당 database의 버전을 관리하기 위해 필요한 값이다.
2. ObjectStore 생성
// Create ObjectStore
request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
const upgradeDb = (event.target as IDBOpenDBRequest).result;
const objectStore = upgradeDb.createObjectStore("user", { keyPath: "id", autoIncrement: true });
objectStore.createIndex("email", "email", { unique: true });
};
onupgradeneeded는 version이 변경될 때마다 동작한다.
이 코드에서는 처음 생성될 때 version 1이 할당되면서 단 한 번 callback 함수를 실행한다.
이 특성을 활용해 내가 사용할 objectStore을 create 하는 callback 함수를 작성했다.
createObjectStore은 총 2개의 인자를 받는다.
(name: string, options?: IDBObjectStoreParameters | undefined)
keyPath란 key로 사용할 객체 프로퍼티이고, autoIncrement를 활용해 자동으로 늘어나는 key 값을 만들어주었다.
또한 createIndex 메서드를 활용해 email을 index로 적용해주었다.
IndexedDB CRUD
1. Add
// Add Data
const addBtnHandler = () => {
if(!userData.age || !userData.email || !userData.name) return alert("입력값을 모두 채워주세요");
const store = db.transaction("user", "readwrite").objectStore("user");
const addReq = store.add(userData);
addReq.onsuccess = () => {
console.log("Add Success!");
setUserData(DEFAULT_USER_DATA);
};
};
입력값을 받아 db에 data를 create 해보자. 우선 transaction을 통해 어느 objectStore에 어떤 mode로, 접근할지 작성한다. mode는 'readwrite'와 'readonly' 두가지가 있다. 나는 새 데이터를 추가할 것이기 때문에 readwrite 옵션을 통해 add 메서드를 수행하도록 작성했다.
2. Get / GetAll
다음은 db에 기록된 데이터를 get 해오자. 우선 내가 구현한 get 방식은 총 3가지이다.
- key를 통한 get
- index를 통한 get
- 전체 data get
// Get Data by Key
const getByIdBtnHandler = () => {
const id = Number(prompt("ID를 입력하세요"));
const store = db.transaction("user", "readonly").objectStore("user");
const getReq = store.get(id);
getReq.onsuccess = () => {
if (getReq.result as UserDataType) {
console.log("Get Success!");
setUserGetData([getReq.result]);
return;
}
return alert("해당 ID가 존재하지 않습니다.");
};
getReq.onerror = () => {
alert("해당 ID가 존재하지 않습니다.");
};
};
key를 통한 get은 간단하다. transaction 구문을 작성한 뒤 get 메서드를 활용해 특정 key 값을 불러온다.
// Get Data by Index
const getByIndexBtnHandler = () => {
const email = prompt('Email을 입력하세요')!;
const store = db.transaction("user", "readonly").objectStore("user");
const index = store.index("email");
const getReq = index.get(email);
getReq.onsuccess = () => {
if (getReq.result as UserDataType) {
console.log("Get Success!");
setUserGetData([getReq.result]);
return;
}
return alert("해당 이메일을 가진 사용자를 찾을 수 없습니다.");
};
};
index를 활용한 get 방식이다. 앞서 email을 index로 만들어주었기 때문에, 해당 값을 활용해 get을 해올 수 있다.
// Get All Data
const getAllBtnHandler = () => {
const store = db.transaction("user", "readonly").objectStore("user");
const getReq = store.getAll();
getReq.onsuccess = () => {
if (getReq.result as Array<UserDataType>) {
console.log("Get Success!");
setUserGetData(getReq.result);
return;
};
return alert("데이터가 존재하지 않습니다.");
};
};
마지막으로 getAll 메서드를 활용해 모든 data를 조회해올 수 있다. 하지만 data 양이 커질수록 부하가 올 수 있기 때문에 IDBCursor를 활용해 Pagination한 data를 불러오는 것이 권장된다.
3. PUT
// Update Data
const updatedBtnHandler = () => {
const store = db.transaction("user", "readwrite").objectStore("user");
const putReq = store.put({...userUpdateData, id: +userUpdateData.id});
putReq.onsuccess = () => {
alert('데이터가 수정되었습니다.');
};
putReq.onerror = () => {
alert('입력한 ID가 존재하지 않습니다.');
};
};
Data를 수정하는 것은 어렵지 않다. Put 메서드에 데이터 형식만 매치시키면 손쉽게 변경이 가능하다.
4. Delete
// Delete Data
const deleteBtnHandler = () => {
const id = prompt('삭제할 ID를 입력하세요')!
const store = db.transaction("user", "readwrite").objectStore("user");
const deleteReq = store.delete(+id);
deleteReq.onsuccess = () => {
alert('데이터가 삭제되었습니다.');
};
deleteReq.onerror = () => {
alert('입력한 ID가 존재하지 않습니다.');
};
};
삭제 또한 key 값을 활용해 삭제할 수 있다.
전체 코드
그리고 앞으로 할 일...
프로젝트에 적용하기
1. Provider 만들기
2. customHook을 활용해 data handler 만들기
comments
loading…