본문 바로가기
개발/React

리액트 - Indexed DB로 로컬에 데이터 저장하기 (How to use Indexed DB in React)

by 피로물든딸기 2023. 7. 22.
반응형

리액트 전체 링크

 

참고

- 로컬 스토리지 사용 방법과 세션 스토리지 비교

 

Indexed DB는 로컬 스토리지보다 사용이 까다롭지만, 데이터 용량이 큰 장점이 있다.

로컬 스토리지로 감당이 불가능한 경우는 Indexed DB를 이용해보자.


템플릿 만들기

 

데이터 (ID, name, email, age)를 add, get, delete + print all data 기능을 만들어보자.

아래와 같이 템플릿을 간단히 만들어보자.

 

코드는 아래를 참고하자.

import React, { useEffect, useState } from "react";

import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import { TextField } from "@mui/material";

const App = () => {
  const [id, setID] = useState("");
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [age, setAge] = useState("");

  const initDB = () => {};
  const addDataToIndexedDB = () => {};
  const getDataFromIndexedDB = () => {};
  const deleteDataFromIndexedDB = () => {};
  const getAllDataFromIndexedDB = () => {};

  return (
    <div>
      <h1 style={{ marginLeft: 20 }}>Indexed DB Test</h1>
      <Stack sx={{ m: 5 }} spacing={2} direction="row">
        <Button variant="outlined" color="primary" onClick={addDataToIndexedDB}>
          저장하기
        </Button>
        <Button
          variant="outlined"
          color="secondary"
          onClick={getDataFromIndexedDB}
        >
          불러오기
        </Button>
        <Button
          variant="outlined"
          color="warning"
          onClick={deleteDataFromIndexedDB}
        >
          삭제하기
        </Button>
        
        <Button variant="outlined" color="success" onClick={getAllDataFromIndexedDB}>
          모든 데이터 출력
        </Button>
      </Stack>
      <Stack sx={{ m: 5 }} spacing={2} direction="row">
        <TextField
          id="outlined-required"
          label="ID"
          value={id}
          onChange={(e) => setID(e.target.value)}
        />
        <TextField
          id="outlined-required"
          label="name"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
        <TextField
          id="outlined-required"
          label="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        <TextField
          id="outlined-required"
          label="age"
          value={age}
          onChange={(e) => setAge(e.target.value)}
        />
      </Stack>
    </div>
  );
};

export default App;

init DB

 

브라우저에서 indexed DB 지원 여부는 아래의 변수가 존재하는지 확인하면 된다.

  const idb =
    window.indexedDB ||
    window.mozIndexedDB ||
    window.webkitIndexedDB ||
    window.msIndexedDB ||
    window.shimIndexedDB;

 

이제 testDBuserDB store를 만들어보자.

  const initDB = () => {
    if (!idb) {
      console.log("Indexed DB 미지원 브라우저");
      return;
    }

    const request = idb.open("testDB", 2);

    request.onerror = (e) => {
      console.log("error", e);
    };

    request.onupgradeneeded = (e) => {
      const db = e.target.result;
      console.log(db);

      if (!db.objectStoreNames.contains("userDB")) {
        const objectStore = db.createObjectStore("userDB", { keyPath: "id" });
      }
    };

    request.onsuccess = (e) => {
      console.log("db open");
    };
  }

 

DB를 open할 때, DB의 이름과 ver(= 2)을 명시한다.

const request = idb.open("testDB", 2);

 

이후 각 경우에 대해 필요한 내용을 구현한다.

여기서는 userDB Store가 존재하지 않는다면 만들도록 구현하였다. (onupgradeneeded)

    request.onerror = (e) => {
      console.log("error", e);
    };

    request.onupgradeneeded = (e) => {
      const db = e.target.result;
      console.log(db);

      if (!db.objectStoreNames.contains("userDB")) {
        const objectStore = db.createObjectStore("userDB", { keyPath: "id" });
      }
    };

    request.onsuccess = (e) => {
      console.log("db open");
    };

 

useEffect로 initDB를 실행시켜보자.

  useEffect(() => initDB(), []);

 

Application에서 IndexedDB 아래에 testDBuserDB Store가 생성된 것을 확인할 수 있다.


Add Data

 

데이터 추가 코드는 다음과 같다.

userDB Store는 keyPath를 "id"로 하였기 때문에 put할 때, id를 반드시 넣어야 한다.

db에서 transaction할 store를 넣고 readwrite로 설정한다.

무사히 DB가 추가되어 트랜잭션이 종료되었다면 DB를 종료한다.

  const addDataToIndexedDB = () => {
    const dbOpen = idb.open("testDB", 2);

    dbOpen.onsuccess = () => {
      const db = dbOpen.result;
      const transaction = db.transaction("userDB", "readwrite");
      const userDB = transaction.objectStore("userDB");

      const users = userDB.put({
        id,
        name,
        email,
        age,
      });

      users.onsuccess = () => {
        transaction.oncomplete = () => {
          db.close();
        };
      };

      users.onerror = (e) => {
        console.log("error", e);
      };
    };
  };

 

DB에 데이터를 추가한 후, IndexedDB에 잘 반영되는지 확인해보자.


Get Data

 

transaction 옵션은 readonly가 되고, get에 key를 넣어주면 된다.

srcElement의 result에 결과가 존재한다.

  const getDataFromIndexedDB = () => {
    const dbOpen = idb.open("testDB", 2);

    dbOpen.onsuccess = () => {
      let db = dbOpen.result;
      const transaction = db.transaction("userDB", "readonly");
      const userDB = transaction.objectStore("userDB");
      const users = userDB.get(id);

      users.onsuccess = (e) => {
        let value = e.srcElement.result;
        if (value) {
          setName(value.name);
          setEmail(value.email);
          setAge(value.age);
        }
      };

      users.onerror = (e) => {
        console.log("error", e);
      };

      transaction.oncomplete = () => {
        db.close();
      };
    };
  };

 

데이터 조회가 정상적으로 되는지 확인해 보자.

 

모든 데이터를 조회하고 싶다면 get 대신 getAll 메서드를 사용하면 된다. 

const users = userDB.getAll();

 

전체 조회 코드는 다음과 같다.

  const getAllDataFromIndexedDB = () => {
    const dbOpen = idb.open("testDB", 2);

    dbOpen.onsuccess = () => {
      let db = dbOpen.result;
      const transaction = db.transaction("userDB", "readonly");
      const userDB = transaction.objectStore("userDB");
      const users = userDB.getAll();

      users.onsuccess = (e) => {
        console.log(e);
        console.log(e.srcElement.result);
      };

      users.onerror = (e) => {
        console.log("error", e);
      };

      transaction.oncomplete = () => {
        db.close();
      };
    };
  };


Delete Data

 

delete는 데이터를 삭제하기 때문에 트랜잭션 옵션이 readwrite가 된다.

  const deleteDataFromIndexedDB = () => {
    const dbOpen = idb.open("testDB", 2);

    dbOpen.onsuccess = () => {
      let db = dbOpen.result;
      const transaction = db.transaction("userDB", "readwrite");
      const userDB = transaction.objectStore("userDB");
      const users = userDB.delete(id);

      users.onsuccess = (e) => {
        console.log("delete!", e);
      };

      users.onerror = (e) => {
        console.log("error", e);
      };

      transaction.oncomplete = () => {
        db.close();
      };
    };
  };

 

정상적으로 삭제되는 것을 확인해보자.

 

전체 코드는 다음과 같다.

import React, { useEffect, useState } from "react";

import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import { TextField } from "@mui/material";

const App = () => {
  const [id, setID] = useState("");
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [age, setAge] = useState("");

  const idb =
    window.indexedDB ||
    window.mozIndexedDB ||
    window.webkitIndexedDB ||
    window.msIndexedDB ||
    window.shimIndexedDB;

  useEffect(() => initDB(), []);

  const initDB = () => {
    if (!idb) {
      console.log("Indexed DB 미지원 브라우저");
      return;
    }

    const request = idb.open("testDB", 2);

    request.onerror = (e) => {
      console.log("error", e);
    };

    request.onupgradeneeded = (e) => {
      const db = e.target.result;
      console.log(db);

      if (!db.objectStoreNames.contains("userDB")) {
        const objectStore = db.createObjectStore("userDB", { keyPath: "id" });
      }
    };

    request.onsuccess = (e) => {
      console.log("db open");
    };
  };

  const addDataToIndexedDB = () => {
    const dbOpen = idb.open("testDB", 2);

    dbOpen.onsuccess = () => {
      const db = dbOpen.result;
      const transaction = db.transaction("userDB", "readwrite");
      const userDB = transaction.objectStore("userDB");

      const users = userDB.put({
        id,
        name,
        email,
        age,
      });

      users.onsuccess = () => {
        transaction.oncomplete = () => {
          db.close();
        };
      };

      users.onerror = (e) => {
        console.log("error", e);
      };
    };
  };

  const getDataFromIndexedDB = () => {
    const dbOpen = idb.open("testDB", 2);

    dbOpen.onsuccess = () => {
      let db = dbOpen.result;
      const transaction = db.transaction("userDB", "readonly");
      const userDB = transaction.objectStore("userDB");
      const users = userDB.get(id);

      users.onsuccess = (e) => {
        let value = e.srcElement.result;
        if (value) {
          setName(value.name);
          setEmail(value.email);
          setAge(value.age);
        }
      };

      users.onerror = (e) => {
        console.log("error", e);
      };

      transaction.oncomplete = () => {
        db.close();
      };
    };
  };

  const deleteDataFromIndexedDB = () => {
    const dbOpen = idb.open("testDB", 2);

    dbOpen.onsuccess = () => {
      let db = dbOpen.result;
      const transaction = db.transaction("userDB", "readwrite");
      const userDB = transaction.objectStore("userDB");
      const users = userDB.delete(id);

      users.onsuccess = (e) => {
        console.log("delete!", e);
      };

      users.onerror = (e) => {
        console.log("error", e);
      };

      transaction.oncomplete = () => {
        db.close();
      };
    };
  };

  const getAllDataFromIndexedDB = () => {
    const dbOpen = idb.open("testDB", 2);

    dbOpen.onsuccess = () => {
      let db = dbOpen.result;
      const transaction = db.transaction("userDB", "readonly");
      const userDB = transaction.objectStore("userDB");
      const users = userDB.getAll();

      users.onsuccess = (e) => {
        console.log(e);
        console.log(e.srcElement.result);
      };

      users.onerror = (e) => {
        console.log("error", e);
      };

      transaction.oncomplete = () => {
        db.close();
      };
    };
  };

  return (
    <div>
      <h1 style={{ marginLeft: 20 }}>Indexed DB Test</h1>
      <Stack sx={{ m: 5 }} spacing={2} direction="row">
        <Button variant="outlined" color="primary" onClick={addDataToIndexedDB}>
          추가하기
        </Button>
        <Button
          variant="outlined"
          color="secondary"
          onClick={getDataFromIndexedDB}
        >
          불러오기
        </Button>
        <Button
          variant="outlined"
          color="warning"
          onClick={deleteDataFromIndexedDB}
        >
          삭제하기
        </Button>

        <Button
          variant="outlined"
          color="success"
          onClick={getAllDataFromIndexedDB}
        >
          모든 데이터 출력
        </Button>
      </Stack>
      <Stack sx={{ m: 5 }} spacing={2} direction="row">
        <TextField
          id="outlined-required"
          label="ID"
          value={id}
          onChange={(e) => setID(e.target.value)}
        />
        <TextField
          id="outlined-required"
          label="name"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
        <TextField
          id="outlined-required"
          label="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        <TextField
          id="outlined-required"
          label="age"
          value={age}
          onChange={(e) => setAge(e.target.value)}
        />
      </Stack>
    </div>
  );
};

export default App;

async await ver

 

indexedDB와 관련된 메서드는 indexeddblibrary.js에 분리하고 Promise를 이용하여 아래와 같이 구현한다.

const DB_NAME = "testDB";
const STORE_NAME = "userDB";

function openIndexedDB() {
  return new Promise((resolve, reject) => {
    const request = window.indexedDB.open(DB_NAME, 2);

    request.onerror = (e) => {
      reject("IndexedDB 접근 오류", e);
    };

    request.onsuccess = (e) => {
      const db = e.target.result;
      resolve(db);
    };

    request.onupgradeneeded = (e) => {
      const db = e.target.result;
      const objectStore = db.createObjectStore(STORE_NAME, {
        keyPath: "id",
      });
    };
  });
}

export async function addDataToIndexedDB(data) {
  const db = await openIndexedDB();

  return new Promise((resolve, reject) => {
    const transaction = db.transaction(STORE_NAME, "readwrite");
    const objectStore = transaction.objectStore(STORE_NAME);

    const request = objectStore.put(data);

    request.onsuccess = () => {
      resolve("data added!");
    };

    request.onerror = (e) => {
      console.log("error", e);
    };

    transaction.oncomplete = () => {
      db.close();
    };
  });
}

export async function getDataFromIndexedDB(key) {
  const db = await openIndexedDB();

  return new Promise((resolve, reject) => {
    const transaction = db.transaction(STORE_NAME, "readonly");
    const objectStore = transaction.objectStore(STORE_NAME);

    const request = objectStore.get(key);

    request.onsuccess = (e) => {
      const data = e.target.result;
      resolve(data);
    };

    request.onerror = (e) => {
      reject("error", e);
    };

    transaction.oncomplete = () => {
      db.close();
    };
  });
}

export async function deleteDataToIndexedDB(key) {
  const db = await openIndexedDB();

  return new Promise((resolve, reject) => {
    const transaction = db.transaction(STORE_NAME, "readwrite");
    const objectStore = transaction.objectStore(STORE_NAME);

    const request = objectStore.delete(key);
    console.log(request);
    request.onsuccess = () => {
      resolve("delete ok!");
    };

    request.onerror = (e) => {
      reject("error", e);
    };

    transaction.oncomplete = () => {
      db.close();
    };
  });
}

export async function getAllDataFromIndexedDB() {
  const db = await openIndexedDB();

  return new Promise((resolve, reject) => {
    const transaction = db.transaction(STORE_NAME, "readonly");
    const objectStore = transaction.objectStore(STORE_NAME);

    const request = objectStore.getAll();

    request.onsuccess = (e) => {
      const data = e.target.result;
      resolve(data);
    };

    request.onerror = (e) => {
      reject("데이터 조회 중 오류가 발생했습니다.");
    };

    transaction.oncomplete = () => {
      db.close();
    };
  });
}

 

이제 App.js의 메서드 구현 부분은 asyncawait으로 간결하게 처리할 수 있다.

  const addDataToIndexedDB = async () => {
    let data = {id, name, email, age};
    let result = await idblib.addDataToIndexedDB(data);
    console.log(result);
  };

  const getDataFromIndexedDB = async () => {
    let value = await idblib.getDataFromIndexedDB(id);
    if (value) {
      setName(value.name);
      setEmail(value.email);
      setAge(value.age);
    }
  };

  const deleteDataFromIndexedDB = async () => {
    let result = await idblib.deleteDataToIndexedDB(id);
    console.log(result);
  };

  const getAllDataFromIndexedDB = async () => {
    let value = await idblib.getAllDataFromIndexedDB();
    console.log(value);
  };

 

전체 코드는 다음과 같다.

import React, { useState } from "react";

import Button from "@mui/material/Button";
import Stack from "@mui/material/Stack";
import { TextField } from "@mui/material";

import * as idblib from "./indexeddblibrary.js";

const App = () => {
  const [id, setID] = useState("");
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [age, setAge] = useState("");

  const addDataToIndexedDB = async () => {
    let data = {id, name, email, age};
    let result = await idblib.addDataToIndexedDB(data);
    console.log(result);
  };

  const getDataFromIndexedDB = async () => {
    let value = await idblib.getDataFromIndexedDB(id);
    if (value) {
      setName(value.name);
      setEmail(value.email);
      setAge(value.age);
    }
  };

  const deleteDataFromIndexedDB = async () => {
    let result = await idblib.deleteDataToIndexedDB(id);
    console.log(result);
  };

  const getAllDataFromIndexedDB = async () => {
    let value = await idblib.getAllDataFromIndexedDB();
    console.log(value);
  };

  return (
    <div>
      <h1 style={{ marginLeft: 20 }}>Indexed DB Test</h1>
      <Stack sx={{ m: 5 }} spacing={2} direction="row">
        <Button variant="outlined" color="primary" onClick={addDataToIndexedDB}>
          추가하기
        </Button>
        <Button
          variant="outlined"
          color="secondary"
          onClick={getDataFromIndexedDB}
        >
          불러오기
        </Button>
        <Button
          variant="outlined"
          color="warning"
          onClick={deleteDataFromIndexedDB}
        >
          삭제하기
        </Button>

        <Button
          variant="outlined"
          color="success"
          onClick={getAllDataFromIndexedDB}
        >
          모든 데이터 출력
        </Button>
      </Stack>
      <Stack sx={{ m: 5 }} spacing={2} direction="row">
        <TextField
          id="outlined-required"
          label="ID"
          value={id}
          onChange={(e) => setID(e.target.value)}
        />
        <TextField
          id="outlined-required"
          label="name"
          value={name}
          onChange={(e) => setName(e.target.value)}
        />
        <TextField
          id="outlined-required"
          label="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        <TextField
          id="outlined-required"
          label="age"
          value={age}
          onChange={(e) => setAge(e.target.value)}
        />
      </Stack>
    </div>
  );
};

export default App;
반응형

댓글