いつもlocalStorage使ってたけど、IndexedDBを使ってみる（データベースの作成と情報の取得編）

どちらもデータをJavaScriptを利用してブラウザにデータを保存する仕組みです。
データを永続的に管理する

:::note info
【この記事で書くこと】
・lacalStorageとIndexedDBの違い
・IndexedDBでの実装方法
:::

前提として今回はReactのフレームワークであるNext.jsのプロジェクトかつTypeScriptを利用しておりますのでご認識ください。

## IndexedDBとlocalStorageの違いは？
基本的に、IndexedDBはlocalStorageの上位互換と考えて良いと考えます！
IndexedDBはlocalStorageと比較すると少し導入ハードルが高い印象です…。

:::note info
データの保存形式にも違いがあるので、TODOリストなどを勉強で作る際はIndexedDBの方が適している印象です！
:::

それぞれの違いについて解説します！

### 保存できるデータ構造
IndexedDBは、オブジェクト指向データベースのような構造化されたデータを保存することができ、
localStorageは文字列で保管することができます。

シンプルなデータを保存する時はlocalStorageでも十分だと言えます！

### 容量
ブラウザによって異なるようですが、IndexedDBの方が保存できる容量は大きいです。
localStorageは5-10MB程度に対して、IndexedDBは数百MBから数GBです。

### セキュリティ
IndexedDBは他のドメインからのアクセスが制限されます。
クロスサイトスクリプティングなどの危険が高まるのでセキュリティを重視したい場合はIndexedDBを選ぶと良いです。


## 実装方法
今回の実装は、DBの作成と作成したDBから情報の取得と表示の部分を解説します！

- IndexedDBにデータベースの作成
- データベースから情報を取得する

### IndexedDBの作成
コードで書くと下記の通りになります。

#### データベースを作る
IndexedDBを利用する際はDBの名前とバージョンを指定します。
今回は下記のように指定しました。

```tsx
// 定数に入れても入れなくても問題ないと思いますが、今回は定数に入れています。
const DB_NAME = 'myDB';
const DB_VERSION = 3;

// 処理が成功しなかった場合を考慮してエラーをキャッチできるようにtryを利用します
try {
  const request = window.indexedDB.open(DB_NAME, DB_VERSION);

  request.onupgradeneeded = (event) => {
    // event.targetをキャストしないと型エラーで怒られます
    const db = (event.target as IDBRequest).result as IDBDatabase;
    // storeの中にmanazinesなかった場合に新しく作成するように処理をしています
    if (!db.objectStoreNames.contains('magazines')) {
      db.createObjectStore('magazines', { keyPath: 'id' });
    }
  };

  request.onsuccess = (event) => {
    const db = (event.target as IDBRequest).result as IDBDatabase;
    resolve(db);
  };

  request.onerror = (event) => {
    reject((event.target as IDBRequest).error);
  };
} catch (error) {
  reject(error);
}
```

#### データベースから情報の取得
IndexedDBからデータを取得するためにオープンしてどんな情報を取得するのかを指定します。
今回のコードはNext.jsプロジェクトのpage.tsxで実行しています。

```tsx
import { openDB } from '@/utils/indexedDB';

// IndexedDBに入っているデータ構造をtypeで定義します
type Magazine = {
  id: number;
  title: string;
  numberOfTurns: number;
};

export default function Home() {
const [magazinesData, setMagazinesData] = useState<Magazine[]>([]);

useEffect(() => {
    // 非同期関数として定義します
    const fetchDataFromIndexedDB = async () => {
      try {
        /*
          * この辺りはDBに接続するためのおまじないのような箇所です
          * openDBを実行、DB名を指定してモード、今回は書き込みができるreadwriteを指定
          * ストアを指定、最後にリクエストどんな情報がほしいかを指定
        */
        const db = await openDB();
        const transaction = db.transaction(['magazines'], 'readwrite');
        const objectStore = transaction.objectStore('magazines');
        const request = objectStore.getAll();

        // 作成したリクエストが成功した時の処理
        request.onsuccess = (event) => {
          if (event.target) {
            const result = (event.target as IDBRequest).result as Magazine[];
            setMagazinesData(result);
          }
        };
      } catch (error) {
        console.error('IndexedDBのオープンに失敗しました:', error);
      }
    };

    fetchDataFromIndexedDB();
  // useEffectの第二引数に空の配列を指定することで初回のみ処理が実行されるようにします
  }, []);

  /*
   * useStateを利用してDBの情報を格納したので中身があった場合にレンダリングされるようにListコンポーネントに渡しています
   * &&　の利用には賛否があると思いますが、今回は個人開発かつシンプルに書きたいために使っています
  */
  return (
    <main className="px-2 py-24 max-w-screen-sm m-auto">
      {magazinesData.length > 0 && <List magazines={magazinesData} />}
      <AddButton />
    </main>
  );
}
```

#### 取得した情報を表示する
先ほどListコンポーネントに渡したデータを表示するコードは以下の通りです。

```tsx
// DBのデータの型を定義
type Magazine = {
  id: number;
  title: string;
  numberOfTurns: number;
};

// 親コンポーネントから渡されるデータの型を定義
type ListProps = {
  magazines: Magazine[];
};

/* 
 * mapを利用して親コンポーネントから渡されたデータを展開します
 * ListItemコンポーネントに取得した情報の1つを渡してliとして表示していきます
*/
const List = ({ magazines }: ListProps) => {
  return (
    <section className="mb-8">
      <ul>
        {magazines.map((magazine) => (
          <ListItem
            key={magazine.id}
            id={magazine.id}
            magazineTitle={magazine.title}
            magazineNumberOfTurns={magazine.numberOfTurns}
          />
        ))}
      </ul>
    </section>
  );
};
```

受け取った側の子コンポーネントで表示処理を書くだけで情報が表示できます。

```tsx
import React, { useState, useEffect } from 'react';
import { openDB } from '@/utils/indexedDB';

type Props = {
  id: number;
  magazineTitle: string;
  magazineNumberOfTurns: number;
};

const ListItem = ({ id, magazineTitle, magazineNumberOfTurns }: Props) => {

  return (
    <li>
      <p>{id}</p>
      <p>{magazineTitle}</p>
      <p>{magazineNumberOfTurns}</p>
    </li>
  );
};
```

## まとめ
firebaseなどを利用するよりは比較的容易に扱えるデータベースかつ、構造化されたデータを保存できるので個人のアプリケーション開発をする場合には十分だと感じました。

これまでlocalStorageしか使ってこなかった方には少し煩わしく感じる部分もあると思います。
ただ、データを構造化して　保存できるというのはTODOリストなどのアプリケーションを作成するのには適しているのではないかと感じました！

FireBaseなどのDBを利用するよりは圧倒的に楽に実装ができるので個人開発なのであれば検討しても良いのではと感じました！

少し長くなってしまったので、IndexedDBへの情報の追加や更新については次回の記事で紹介させていただきたいと思います！


## 参考資料
https://developer.mozilla.org/ja/docs/Web/API/Window/localStorage

https://developer.mozilla.org/ja/docs/Web/API/IndexedDB_API

https://ja.javascript.info/indexeddb