JavaScript 逆引き集 | IndexedDB 基本(簡易)

JavaScript JavaScript
スポンサーリンク

IndexedDB 基本(簡易)— const req = indexedDB.open('db')

IndexedDB はブラウザ内に大量・構造化データを保存できるクライアントサイドDBです。非同期APIでトランザクションを使用し、オブジェクトストア(テーブルのようなもの)にキーと値を保存します。WebStorageより大容量・高機能で、オフラインでも扱えるのが特徴です。


開く・初期化の基本(onupgradeneeded でスキーマ作成)

<script>
  const DB_NAME = "myDB";
  const DB_VERSION = 1; // スキーマ変更時に上げる
  let db;

  const req = indexedDB.open(DB_NAME, DB_VERSION);

  // 初回作成やバージョンアップ時に呼ばれる(ストアやインデックスの定義)
  req.onupgradeneeded = (event) => {
    db = event.target.result;
    // 既存チェック
    if (!db.objectStoreNames.contains("todos")) {
      const store = db.createObjectStore("todos", { keyPath: "id" }); // 主キー
      store.createIndex("by_done", "done", { unique: false });        // インデックス
    }
  };

  // 開けたら使えるDBハンドルを受け取る
  req.onsuccess = (event) => {
    db = event.target.result;
    console.log("DB opened");
  };

  // エラー処理
  req.onerror = (event) => {
    console.error("DB open error:", event.target.error);
  };
</script>
HTML
  • indexedDB.open(name, version) は IDBOpenDBRequest を返し、onsuccess/onerror/onupgradeneeded で状態を受け取ります。
  • バージョンは「スキーマ管理」に使い、ストアやインデックスの追加・変更時に上げます。

追加・取得・更新・削除(CRUD)のテンプレート

// トランザクションを開始してストアを取得
function txStore(db, storeName, mode = "readonly") {
  const tx = db.transaction(storeName, mode);
  return { tx, store: tx.objectStore(storeName) };
}

// 追加(put は新規/更新を兼ねる)
function addTodo(db, todo) {
  return new Promise((resolve, reject) => {
    const { tx, store } = txStore(db, "todos", "readwrite");
    const req = store.put(todo); // {id, text, done}
    req.onsuccess = () => resolve(req.result);
    req.onerror = () => reject(req.error);
  });
}

// 主キーで取得
function getTodo(db, id) {
  return new Promise((resolve, reject) => {
    const { store } = txStore(db, "todos");
    const req = store.get(id);
    req.onsuccess = () => resolve(req.result ?? null);
    req.onerror  = () => reject(req.error);
  });
}

// インデックスで検索
function getDoneTodos(db) {
  return new Promise((resolve, reject) => {
    const { store } = txStore(db, "todos");
    const idx = store.index("by_done");
    const req = idx.getAll(true); // done === true の全件
    req.onsuccess = () => resolve(req.result);
    req.onerror  = () => reject(req.error);
  });
}

// 削除
function deleteTodo(db, id) {
  return new Promise((resolve, reject) => {
    const { tx, store } = txStore(db, "todos", "readwrite");
    const req = store.delete(id);
    req.onsuccess = () => resolve();
    req.onerror  = () => reject(req.error);
  });
}
JavaScript
  • IndexedDB は非同期APIで、各操作はリクエストに対する onsuccess/onerror で結果を受けます。データ整合性のため、全操作はトランザクション内で行われます。

例題:ミニTODO(追加→一覧→トグル)

<div>
  <input id="text" placeholder="todo">
  <button id="add">追加</button>
  <ul id="list"></ul>
</div>
<script>
  const DB_NAME = "myDB"; const DB_VERSION = 1;
  let db;

  const req = indexedDB.open(DB_NAME, DB_VERSION);
  req.onupgradeneeded = (e) => {
    db = e.target.result;
    if (!db.objectStoreNames.contains("todos")) {
      const store = db.createObjectStore("todos", { keyPath: "id" });
      store.createIndex("by_done", "done", { unique: false });
    }
  };
  req.onsuccess = (e) => { db = e.target.result; render(); };

  async function render() {
    const { store } = txStore(db, "todos");
    const allReq = store.getAll();
    allReq.onsuccess = () => {
      const list = document.getElementById("list");
      list.innerHTML = "";
      allReq.result.forEach(item => {
        const li = document.createElement("li");
        li.textContent = `${item.text} ${item.done ? "✅" : ""}`;
        li.addEventListener("click", async () => {
          item.done = !item.done;
          await addTodo(db, item);
          render();
        });
        list.appendChild(li);
      });
    };
  }

  document.getElementById("add").addEventListener("click", async () => {
    const text = document.getElementById("text").value.trim();
    if (!text) return;
    await addTodo(db, { id: crypto.randomUUID(), text, done: false });
    document.getElementById("text").value = "";
    render();
  });

  function txStore(db, storeName, mode = "readonly") {
    const tx = db.transaction(storeName, mode);
    return { tx, store: tx.objectStore(storeName) };
  }
  function addTodo(db, todo) {
    return new Promise((resolve, reject) => {
      const { store } = txStore(db, "todos", "readwrite");
      const req = store.put(todo);
      req.onsuccess = () => resolve(req.result);
      req.onerror  = () => reject(req.error);
    });
  }
</script>
HTML

実務でのコツ

  • スキーマは onupgradeneeded で定義: オブジェクトストアやインデックスの追加・変更は、バージョンを上げてこのイベント内で行うのが正道です。
  • トランザクションのモード選択: 読み取りは readonly、書き込みは readwrite。同一トランザクション内でまとまった処理を行うと整合性が保てます。
  • Promise ラップで扱いやすく: request-based のコールバックを Promise 化すると、async/await で読みやすくなります。
  • 用途と強み: 大量データ・複雑構造・オフライン対応に強い。WebStorageより高速で容量も大きいのが利点です。
  • バージョン管理の考え方: スキーマ変更時に version をインクリメント。新規ストア作成やインデックス追加はこのタイミングで。

ありがちなハマりと対策

  • open 後に db が未設定: onsuccess が来る前に使おうとして失敗。必ず onsuccess 以降に操作開始。
  • スキーマ変更を実行時にやろうとする: createObjectStore/createIndex は onupgradeneeded の中でのみ可。通常時にはエラー。
  • 同時に複数書き込みが競合: 1つの readwrite トランザクションでまとめる。必要なら待ち合わせの制御を入れる。
  • 検索が遅い: よく使う検索条件にはインデックスを貼る。store.index('name').getAll(key) などで高速化。

直感的な指針

  • まず indexedDB.open(name, version) を呼び、onupgradeneeded でストア・インデックスを作る。onsuccess で db を受け、各操作はトランザクション+リクエストの onsuccess/onerror で扱う。大量・構造化・オフライン向けなら IndexedDB を選ぶ価値がある。
タイトルとURLをコピーしました