【Nuxt3】IndexedDBを使って簡単にアプリを作成する方法

運営者:そうすけ


愛媛出身の30代のブロガー兼ソフトウェアエンジニア
主にフロント・バックの開発を行っています。

趣味はガジェットと植物

一般的なWebアプリはCRUDは必須機能です。

だがミドルウェア、バックエンドの知識が必要になってくるの実装が大変。

そこで簡易的にDB機能を作るのがIndexedDBです。

環境構築も不要でブラウザがあれば簡単に使用できます。

目次

Nuxtでの使用注意点

必ずonmountedを使用すること!

マウント(<template>内がHTMLに変換)され、描画され始めるときに実行するのがonmoutedです。

データの取得前に実行してしまうと、描画するスピードに取得が間に合わず、エラーになります。

初回データベースの作成

定義したデータベースの初回オープン時はonupgradeneededイベントが起動する。

インスタンスを作成してオブジェクトストアを作成

//indexDBはブラウザ側なので、Mount時に初期化させる
onMounted(()=>{

    const openRequest = indexedDB.open(dbName)

    //indexxdDBの初回オープンでは、onupgradeneededイベントが発火する
    //このイベントの中なら、オブジェクトストアを作成したり、
    //DBの構造を変更することができる
    openRequest.onupgradeneeded = (event) =>{

    //コールバック関数からindexDBのインスタンスを作成
    const db = (event.target as IDBRequest).result
    

    //テーブル名とレコードの識別子となるキーの名前を指定して、
    //新たにテーブル(正確にはオブジェクトストア)を作成する
    db.createObjectStore(tableName,{keyPath:"id"});

    };
});

データの追加

//INdexdDBのDB名とテーブル名
const dbName = "recipe-memo"
const tableName = "recipe";

//IndexedDBを使うには、まずIndexedDBを開く必要がある
//なので最初にそのためのリクエストをopen関数で実行する
//その際、引数には使いたい任意のDB名を渡す。
const openRequest = indexedDB.open(dbName);

//IndexedDBの軌道に成功したら、次のコールバック関数を実行
openRequest.onsuccess = (event) => {
    //起動しただけではレシピの保存をできない
    //まずコールバック関数の引数からIndexedDBのインスタンスを取得する
    //なお型推論が弱いので、より厳密な方を明示している
    const db =(event.target as IDBRequest).result;

    //トランザクションを開始
    const transaction =db.transaction(tableName,"readwrite");
  
    //テーブル名を指定して、IndexedDBからテーブルを取得する
    //(厳密にはテーブルでなくオブジェクトすとあ)
    //なおこのテーブルは親が事前に作成しておく前提
    //型推論が弱いので、厳密な方を明示
    const table =transaction.objectStore(tableName) as IDBObjectStore;
    
    //ここまででIndexedDBへの操作が可能になる
   
    //親から渡されたレシピID
    const id = props.id;

    //テーブルへのレシピ保存を試行する
    const putRequest = table.put({
        //親がidをindexedDBのキーとして使える用準備してある前提
        id,
        name,
        items: items.map((i) => ({
            //仕様上、シリアライズ可能値にすべきなのでmapする
            name: i.name,
            amount:i.amount,
            unit:i.unit,
        })),
        howToCook,
    });

    //レシピ保存に成功したら、親から渡されたリダイレクト関数を実行
    putRequest.onsuccess = (event) => {
        const menu = (event.target as IDBRequest).result.value;
        console.log(menu);
        alert("保存に成功しました");
        navigateTo(props.redirectOnSuccess);
    };
    //レジピ保存に失敗したらアラートを出す
    putRequest.onerror = () =>{
        alert("保存に失敗しました");
    };
 };

データの取得

こちらもonmounted

全件取得

カーソルを使った方がパフォーマンスがあがる

//DB名とテーブル名
const dbName ="recipe-memo"
const tableName = "recipe"
 
const openRequest = indexedDB.open(dbName)

openRequest.onerror= (event) =>{
     //alert('しっぱい');
  }

openRequest.onsuccess= (event) =>{
    //alert('せいこう');
    //コールバック関数からindexDBのインスタンスを作成
    const db = (event.target as IDBRequest).result;
    
    //閲覧のみなのでreadonlyトランザクションを開始する
    const transaction = db.transaction(tableName,"readonly");    
    const table = transaction.objectStore(tableName) as IDBObjectStore;

    //DB内のレコードを一件ずつ巡回するためのカーソルをリクエスト
    const cursorRequest = table.openCursor();

  cursorRequest.onsuccess = (event) =>{
      //イベント結果を取得
      const cursor = (event.target as IDBRequest).result;
   //カーソルがなかったらリターン
      if(!cursor) return;

      //現在のカーソル位置のレコードを取得してレシピのリンク一覧に追加
      const record = cursor.value;
      links.value.push({
          //idからレシピ詳細ページのURLを作成
          //詳細ページはpages/products/recipe_memo/[id]/index.vueの動的ルート

          url:'/products/recipe_memo/${record.id}',
          //レシピ名
          text:record.name,          
      })
      //次のカーソルに移動 (cursorRequest.onsuccessが呼ばれる)
      cursor.continue();

    }
}

1件取得

//パラメーターからidを取得
const route = useRoute()
const id = route.params.id 

//DB名とテーブル名を宣言
const dbName = "recipe-memo"
const tableName = "recipe"


onMounted(()=>{
  const openRequest = indexedDB.open(dbName)

  openRequest.onerror= (event) =>{
     alert('しっぱい');
  }

  openRequest.onsuccess= (event) =>{
    
    //コールバック関数からindexDBのインスタンスを作成
    const db = (event.target as IDBRequest).result;
    //閲覧のみなのでreadonlyトランザクションを開始する
     const transaction = db.transaction(tableName,"readonly");    
     const table = transaction.objectStore(tableName) as IDBObjectStore;
     //キーに対してvalueを持ってくる
     const request = table.get(id);
    
    request.onerror = (event) => {
    // エラー処理!
    alert('しっぱい');
    };
    request.onsuccess = (event) => {
    // request.result に対して行う処理!
    record.value = (event.target as IDBRequest).result;

    //レコードがない場合
    if(!record.value){
        alert('レシピが見つかりません。アプリのぺージに戻ります');
        navigateTo('/products/recipe_memo');
    }
    };
 }
})

データの削除

//パラメーターからidを取得
const route = useRoute()
const id = route.params.id 

//DB名とテーブル名を宣言
const dbName = "recipe-memo"
const tableName = "recipe"

const openRequest = indexedDB.open(dbName)
    openRequest.onerror= (event) =>{
     alert('しっぱい');
    }

    openRequest.onsuccess= (event) =>{    
        //コールバック関数からindexDBのインスタンスを作成
        const db = (event.target as IDBRequest).result;
        //閲覧のみなのでreadonlyトランザクションを開始する
        const transaction = db.transaction(tableName,"readwrite");    
        const table = transaction.objectStore(tableName) as IDBObjectStore;
        //削除するときはdelete関数を使用
        const request = table.delete(id);
        
        request.onerror = (event) => {
        // エラー処理!
        alert('削除に失敗しました。');
        };

        request.onsuccess = (event) => {
          // request.result に対して行う処理!
            //削除して元ページへ移動
            alert('削除しました。前のページに戻ります');
            goBack()
        };
     }

データの更新

SQLのようにレコードを部分的にUPDATEするのではなく、一度データオブジェクトを取得して変数を変えた後、もう一度put関数で上書きするような挙動になります。

なので新規作成と処理としては同様になります。

//パラメーターからidを取得
const route = useRoute()
const id = route.params.id 

//indexedDBのDB名とテーブル名
const dbName ="recipe-memo";
const tableName = "recipe";

//レコードが入るrefオブジェクトを宣言
//onmountedの前なのでnullを型に入れる、初期値もnull
//const record = ref<RecipeEntity|null>(null);

onMounted(()=>{
  const openRequest = indexedDB.open(dbName)

  openRequest.onerror= (event) =>{
     alert('しっぱい');
  }

  openRequest.onsuccess= (event) =>{
    
    //コールバック関数からindexDBのインスタンスを作成
    const db = (event.target as IDBRequest).result;
    //閲覧のみなのでreadonlyトランザクションを開始する
     const transaction = db.transaction(tableName,"readwrite");    
     const table = transaction.objectStore(tableName) as IDBObjectStore;
     //キーに対してvalueを持ってくる
     const request = table.get(id);
    
    request.onerror = (event) => {
    // エラー処理!
    alert('しっぱい');
    };
    request.onsuccess = (event) => {
    //キー取得に取得すると、対応するvalueがかえってくる    
    // value要素にIndexdbのvalueを代入
    const r = (event.target as IDBRequest).result;
    //戻り値を代入
    form.name = r.name
    form.items = r.items
    form.howToCook = r.howToCook
    };

    //テーブルへのレシピ保存を試行する
    const putRequest = table.put({
        //親がidをindexedDBのキーとして使える用準備してある前提
        id,
        name,
        items: items.map((i) => ({
            //仕様上、シリアライズ可能値にすべきなのでmapする
            name: i.name,
            amount:i.amount,
            unit:i.unit,
        })),
        howToCook,
    });

   //レシピ保存に成功したら、親から渡されたリダイレクト関数を実行
    putRequest.onsuccess = (event) => {
        const menu = (event.target as IDBRequest).result.value;
        console.log(menu);
        alert("保存に成功しました");
        navigateTo(props.redirectOnSuccess);
    };
    //レジピ保存に失敗したらアラートを出す
    putRequest.onerror = () =>{
        alert("保存に失敗しました");
    };
 };



 }
})

まとめ

  • 新規データベース作成→onupgradeneededイベントを使用
  • 新規データ追加→put関数を使用
  • データ取得→全件ならカーソルを使用、一件ならgetを使用
  • データ削除→deleteを使用
  • データ更新→データを取得して書き換えてput関数を使用

参考書籍

よかったらシェアしてね!
  • URLをコピーしました!
  • URLをコピーしました!

この記事を書いた人

コメント

コメントする

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)

目次