프로젝트를 진행하며 장바구니 데이터를 로컬에 저장해두기 위해 적합한 웹 스토리지를 찾던 중, IndexedDB에 대해 알게 되었다. 웹 스토리지 중에 왜 IndexedDB를 선택했는지, 그리고 프로젝트 내에서 어떤 방식으로 구현을 했는지 간단하게 정리해보려 한다.
우선 IndexedDB란 무엇이고 어떤 특징을 가지고 있는지 알아보자!
IndexedDB는 파일이나 블롭 등 많은 양의 구조화된 데이터를 클라이언트에 저장하기 위한 로우 레벨 API이다. 다시 말해, IndexedDB는 브라우저가 제공하는 클라이언트 스토리지 중 구조화된 다량의 데이터를 저장하는데 유용한 브라우저 저장소이다. IndexedDB는 데이터를 자료형으로 저장해 고성능으로 데이터를 탐색할 수 있으며 작업들은 모두 비동기로 이루어진다. 또한 IndexedDB는 Transaction Database System이므로 데이터의 변경은 모두 transaction 내에서 이루어지며 작업을 수행하는 도중 문제가 생겼을 시, 변경 사항을 폐기하고 이전의 데이터 상태로 돌아간다.
IndexedDB는 key - value 형태로 데이터를 저장하여 다량의 데이터를 처리하고 빠르게 데이터에 접근할 수 있다.
장바구니 상품 데이터는 품명, 옵션, 수량, 가격 등 저장해야 될 속성이 많았다. 상품의 고유한 번호인 상품 번호를 인덱스 키로 사용하면 데이터를 효율적으로 처리할 수 있을 것이라 생각했다.
IndexedDB는 여러 타입의 데이터를 저장할 수 있다.
그에 비해 localStorage의 경우엔 문자열 데이터만 저장이 가능했다. TypeScript로 개발하는 프로젝트라 정적 타입을 미리 명시해 사용하는데 스토리지에 값을 저장하고 조회할 때마다 모든 데이터 값의 타입을 변경할 필요가 없다는 것이 큰 장점이었다.
IndexedDB는 여러개의 store로 관리된다.
IndexedDB를 이용해 상품 데이터를 사용하는 페이지는 총 2개의 페이지로 하나는 장바구니 페이지, 또 하나는 결제 페이지였다. 상품 별로 isPayImmediately 같은 키를 만들고 당장 결제할 상품인지 장바구니에 담기만 할 상품인지를 구분할까도 생각하였으나 이런 방식은 DB에 담을 때도 조회를 할 때도 너무 비효율적이라 생각이 들었다. IndexedDB에는 여러개의 object store를 생성할 수 있어 하나의 데이터베이스를 사용해 장바구니 상품과 지금 바로 결제할 상품을 store를 통해 구분할 수 있었다.
IndexedDB를 사용할 때 기본 순서는 아래와 같다.
const addDB = async (data: DataType) => {
const db = await openDB("DB", 1, { // openDB 함수(idb 라이브러리)로 "DB"라는 이름의 버전 1인 DB 오픈, indexedDB.open("DB", 1)
upgrade(db) {
db.createObjectStore("cart", { // "cart"라는 이름의 object store 생성
keyPath: "id", // 고유 키는 id로 지정
});
},
});
db
.transaction("cart", "readwrite")
.objectStore("cart")
.add({
id: data.product?.p_No, // 상품의 고유한 값인 상품 번호를 keyPath id로 사용
productNo: data.product?.p_No,
brand: data.product?.a_Brand,
name: data.product?.p_Name,
price: data.product?.p_Cost,
option: props.optionList,
cnt: props.cnt,
})
}
const getDB = async (data: DataType) => {
const db = await openDB("DB", 1, {
upgrade(db) {
db.createObjectStore("cart", {
keyPath: "id",
});
},
});
db
.transaction("cart", "readonly")
.objectStore("cart")
.getAll() // 특정한 데이터만 조회를 원하면 .get(id)로 조회 가능
}
const updateDB = async (data: DataType) => {
try {
const db = await openDB("DB", 1, {
upgrade(db) {
db.createObjectStore("cart", {
keyPath: "id",
});
},
});
db
.transaction("cart", "readwrite")
.objectStore("cart")
.put({
id: data.product?.p_No,
productNo: data.product?.p_No,
brand: data.product?.a_Brand,
name: data.product?.p_Name,
price: data.product?.p_Cost,
option: props.optionList,
cnt: props.cnt,
})
} catch (error) {
console.log(error)
}
}
const deleteDB = async (id: number) => {
try {
const db = await openDB("DB", 1, {
upgrade(db) {
db.createObjectStore("cart", {
keyPath: "id",
});
},
});
db
.transaction("cart", "readwrite")
.objectStore("cart")
.delete(id);
} catch (error) {
console.log(error);
}
}
IndexedDB는 비동기 작업이기 때문에 try ~ catch
를 이용한 에러 처리가 가능하였다.