장바구니 데이터를 IndexedDB에 저장하기

해솔·2023년 1월 6일
1

프로젝트를 진행하며 장바구니 데이터를 로컬에 저장해두기 위해 적합한 웹 스토리지를 찾던 중, IndexedDB에 대해 알게 되었다. 웹 스토리지 중에 왜 IndexedDB를 선택했는지, 그리고 프로젝트 내에서 어떤 방식으로 구현을 했는지 간단하게 정리해보려 한다.

IndexedDB

우선 IndexedDB란 무엇이고 어떤 특징을 가지고 있는지 알아보자!

IndexedDB는 파일이나 블롭 등 많은 양의 구조화된 데이터를 클라이언트에 저장하기 위한 로우 레벨 API이다. 다시 말해, IndexedDB는 브라우저가 제공하는 클라이언트 스토리지 중 구조화된 다량의 데이터를 저장하는데 유용한 브라우저 저장소이다. IndexedDB는 데이터를 자료형으로 저장해 고성능으로 데이터를 탐색할 수 있으며 작업들은 모두 비동기로 이루어진다. 또한 IndexedDB는 Transaction Database System이므로 데이터의 변경은 모두 transaction 내에서 이루어지며 작업을 수행하는 도중 문제가 생겼을 시, 변경 사항을 폐기하고 이전의 데이터 상태로 돌아간다.

왜 IndexedDB를 선택했는가

  1. IndexedDB는 key - value 형태로 데이터를 저장하여 다량의 데이터를 처리하고 빠르게 데이터에 접근할 수 있다.
    장바구니 상품 데이터는 품명, 옵션, 수량, 가격 등 저장해야 될 속성이 많았다. 상품의 고유한 번호인 상품 번호를 인덱스 키로 사용하면 데이터를 효율적으로 처리할 수 있을 것이라 생각했다.

  2. IndexedDB는 여러 타입의 데이터를 저장할 수 있다.
    그에 비해 localStorage의 경우엔 문자열 데이터만 저장이 가능했다. TypeScript로 개발하는 프로젝트라 정적 타입을 미리 명시해 사용하는데 스토리지에 값을 저장하고 조회할 때마다 모든 데이터 값의 타입을 변경할 필요가 없다는 것이 큰 장점이었다.

  3. IndexedDB는 여러개의 store로 관리된다.
    IndexedDB를 이용해 상품 데이터를 사용하는 페이지는 총 2개의 페이지로 하나는 장바구니 페이지, 또 하나는 결제 페이지였다. 상품 별로 isPayImmediately 같은 키를 만들고 당장 결제할 상품인지 장바구니에 담기만 할 상품인지를 구분할까도 생각하였으나 이런 방식은 DB에 담을 때도 조회를 할 때도 너무 비효율적이라 생각이 들었다. IndexedDB에는 여러개의 object store를 생성할 수 있어 하나의 데이터베이스를 사용해 장바구니 상품과 지금 바로 결제할 상품을 store를 통해 구분할 수 있었다.

구현 코드

IndexedDB를 사용할 때 기본 순서는 아래와 같다.

  1. DB를 열고
  2. store를 생성한 다음
  3. transaction을 시작해 DB 작업을 수행한다.

DB 저장

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,
        })
}

DB 조회

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)로 조회 가능
}

DB 업데이트

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)
    }
}

DB 삭제

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를 이용한 에러 처리가 가능하였다.

0개의 댓글