運営者:そうすけ
愛媛出身の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関数を使用
コメント