1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ブラウザストレージの制約を理解する:LocalStorageとIndexedDBのユースケース別選択ガイド

Posted at

はじめに

Webアプリケーションを開発していると、クライアントサイドでのデータ保存は避けて通れない課題となります。特に以下の制約は多くの開発者を悩ませます:

  • LocalStorageはWebWorkerで使用できない
  • IndexedDBは単独ではウィンドウをまたいでデータを共有できない

この記事では、これらの制約の詳細と実践的な解決策を解説します。

想定する読者

  • WebWorkerを使用したアプリケーションを開発している方
  • 複数タブでのデータ同期が必要なアプリケーションを開発している方
  • ブラウザストレージの選択に悩んでいる方

前提知識

  • JavaScriptの基本的な知識
  • WebWorkerの基本的な理解
  • ブラウザストレージ(LocalStorage, IndexedDB)の基本的な使用経験

LocalStorageの制約を理解する

WebWorkerでLocalStorageが使えない理由

LocalStorageがWebWorkerで使用できない理由は、以下の2つに集約されます:

  1. 設計上の制約

    • WebWorkerは独立したグローバルコンテキストで動作
    • メインスレッドのウィンドウオブジェクトにアクセスできない
  2. セキュリティ上の制約

    • メインスレッドに影響を与える可能性のあるAPIへのアクセスが制限されている
    • DOM操作やLocalStorageなどのウィンドウ関連APIは使用不可

IndexedDBのデータ共有における課題

タブ間でのデータ共有

IndexedDBは強力なクライアントサイドストレージですが、以下の制限があります:

  • 組み込みのタブ間通信機能がない
  • 自動的なデータ同期の仕組みがない

実践的な解決策

1. WebWorkerでのデータ処理が必要な場合

// IndexedDBを使用したWebWorkerの実装例
// worker.js
self.onmessage = async function(e) {
  const db = await openDatabase();
  switch(e.data.type) {
    case 'save':
      await saveData(db, e.data.payload);
      self.postMessage({ type: 'saved' });
      break;
    case 'load':
      const data = await loadData(db);
      self.postMessage({ type: 'loaded', data });
      break;
  }
};

async function openDatabase() {
  return new Promise((resolve, reject) => {
    const request = indexedDB.open('MyDatabase', 1);
    request.onerror = () => reject(request.error);
    request.onsuccess = () => resolve(request.result);
    request.onupgradeneeded = (e) => {
      const db = e.target.result;
      db.createObjectStore('myStore', { keyPath: 'id' });
    };
  });
}

2. タブ間でのデータ同期が必要な場合

以下の2つの方法から選択できます:

A. BroadcastChannel APIの使用

// メインスクリプト内での実装例
const channel = new BroadcastChannel('storage_channel');

// データ更新時
async function updateData(data) {
  await saveToIndexedDB(data);
  channel.postMessage({
    type: 'update',
    data: data
  });
}

// 他のタブからの更新を受信
channel.onmessage = async (event) => {
  if (event.data.type === 'update') {
    // UIの更新など
    updateUI(event.data.data);
  }
};

B. SharedWorkerの使用

// shared-worker.js
let connections = [];
let database = null;

self.onconnect = function(e) {
  const port = e.ports[0];
  connections.push(port);
  
  port.onmessage = async function(e) {
    switch(e.data.type) {
      case 'write':
        await writeData(e.data.payload);
        // 他の接続に変更を通知
        broadcast({
          type: 'updated',
          data: e.data.payload
        });
        break;
    }
  };
};

function broadcast(message) {
  connections.forEach(port => port.postMessage(message));
}

ユースケース別の選択ガイド

  1. シンプルなデータ保存

    • WebWorker不要
    • タブ間同期必要
      → LocalStorage + BroadcastChannel API
  2. 複雑なデータ処理

    • WebWorker必要
    • タブ間同期必要
      → IndexedDB + SharedWorker
      または
      → IndexedDB + BroadcastChannel API
  3. 大量のデータ処理

    • WebWorker必要
    • 高パフォーマンス要求
      → OPFS in WebWorker
      または
      → IndexedDB in WebWorker

パフォーマンスの考慮点

  1. LocalStorage

    • 読み取り: 約0.0052ミリ秒
    • 書き込み: 約0.017ミリ秒
    • メインスレッドをブロックする点に注意
  2. IndexedDB

    • 読み取り: 約0.1ミリ秒
    • 書き込み: 約0.17ミリ秒
    • 非同期処理でメインスレッドをブロックしない

まとめ

ブラウザストレージの選択は、アプリケーションの要件によって大きく変わります:

  • シンプルなデータ保存ならLocalStorage
  • 複雑なデータ処理や大量データならIndexedDB
  • タブ間同期が必要な場合はBroadcastChannelかSharedWorker

これらの制約を理解し、適切なストレージと同期方法を選択することで、効率的なWebアプリケーションの開発が可能になります。

参考資料

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?