2022/06/02
【React】Dexie.jsでIndexedDBを使ってみました
最近はかつてネイティブアプリでしかできなかったような複雑な作業をWEBブラウザ上で可能なWEBアプリが増えてきています。
そういった、複雑な作業を行うWEBアプリでは、ブラウザ側、つまりフロントエンドで大量のデータを扱う必要も出てきます。
多量のデータをブラウザ上で扱う場合、通信が途中で切れてしまった場合などに備えて、ブラウザにデータを保存しておき、次にアクセスしたときにそのデータを読み出して、これまでの作業を引き継ぐという動作も多く求められるようになるので、
ブラウザにどういった形でデータを保存するかということも、フロントエンド開発では工夫していく必要があります。
ある程度(5MBくらい)までであれば localStorage や sessionStorage のような WEB Storage API を使うことで簡単に実装ができますが、
大量のデータを扱うのには不向きです。
大量のデータをブラウザ上で扱えるようにした仕組みが IndexedDB です。
IndexedDB は、オブジェクト指向データベース(OODB)で、
JavaScriptで表現可能なオブジェクト(論理値、数値、文字列、date、オブジェクト、配列、正規表現、undefined、null を含む)をそのままの値で保存することができ、
画像データなどのBlobやファイルなども保存できてしまいます。
WEB Storage では、保存できるデータは文字列のみなので、それ以外のデータは文字列に変換して保存し、読み取るときにも文字列から元のデータに変換する処理が必要になりますが、IndexedDBでは、そのまま扱うことができるので、JavaScriptとの相性は抜群です。
しかしながら、IndexedDB API は そのまま扱おうとすると XMLHTTPRequest と同様のイベントハンドラーを使った構文で、若干わかりづらいプログラムになってしまいます。
例)IndexedDBの仕様|MDN より
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | var db; var request = indexedDB.open("MyTestDatabase"); // エラーが起きたとき request.onerror = function(event) { console.log("Database error: " + event.target.errorCode); }; // 接続に成功したとき request.onsuccess = function(event) { db = event.target.result; }; // 新規もしくはDBが更新されたとき request.onupgradeneeded = function(event) { var db = event.target.result; // DBの構造を定義する var objectStore = db.createObjectStore("name", { keyPath: "id", autoIncrement: true }); objectStore.createIndex("name", "name", { unique: false }); objectStore.createIndex("tel", "tel", { unique: false }); }; |
そのため、多くの IndexedDB を扱うためのラッパーやライブラリが有志達によって開発されており、
その中でも比較的人気なのが、今回ご紹介する Dexie.js です。
Dexie.jsによる記述
Dexie.js を使うと、シンプルな構文で IndexedDB を扱うことができます。
例えば先程の例であれば、以下のように記述します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import Dexie, { Table } from 'dexie'; const db = new Dexie('MyTestDatabase'); // DBの構造を定義する db.version(1).stores({ name: '++id, name, age' }); db.open().then(function (db) { // 接続に成功したとき }).catch (function (err) { // エラーが起きたとき console.log("Database error: " + err); }); |
DBの構造もかんたんに定義でき、接続の流れも Promiseオブジェクトを使った、より自然な流れでプログラミングできます。
DexieをReactで扱う
Dexie は React をサポートしており、React 用のHooksなども用意されているため、
React Hooks のような記述方法で Dexieを使うことができます。
React 環境への Dexie のインストール方法はこちらを参照してください。
DBを定義したファイルを用意する
db.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | import Dexie, { Table } from 'dexie'; export interface Items { id?: number; name: string; tel: string; } export class MySubClassedDexie extends Dexie { items!: Table<Items>; constructor() { super('myDatabase'); this.version(1).stores({ items: '++id, name, tel' }); } } export const db = new MySubClassedDexie(); |
コンポーネントにDBのデータを読み込む
React用のHooksである、useLiveQuery を使って、コンポーネントの中でDBのデータを読み込むことができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | import { useLiveQuery } from "dexie-react-hooks"; // Dexie.jsのReact用Hooks import { db } from './db'; //DBを定義したファイル function App() { // DexieのHooksを使って、DBのデータを読み込む const items = useLiveQuery( () => db.items.toArray() ) || []; return ( <div className="App"> {items.map((item) => ( <p key={item.id}> {item.name}: {item.tel} </p> ))} </div> ) } |
DBにデータを追加する
db.items.add(<データ>) でデータを追加できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | import { useState } form 'react'; import { db } from './db'; function AddItemForm () { [status, setStatus] = useState(''); [name, setName] = useState(''); [tel, setTel] = useState(''); async function addItems() { try { // DBにデータを追加 const _id = await db.items.add({ name, tel }); setStatus(`${formik.values.name} を追加しました。`); } catch (error) { setStatus(`${formik.values.name} を追加できませんでした。: ${error}`); } } return ( <> <p>{status}</p> <p> 名前: <input type="text" value={name} onChange={e => setName(e.target.value)}> TEL: <input type="text" value={tel} onChange={e => setTel(e.target.value)}> <button onClick={_e => addItems()}>追加</button> </p> </> ) } |
Dexie.jsを使えば、怖がらずにIndexedDBを利用した開発ができそうです。
↓Dexie.jsを使って作ってみたDEMO
https://codesandbox.io/s/react-dexie-js-mui-j4k3n3
参考にさせていただいたサイト
Author Profile
NINOMIYA
Webデザイナー兼コーダー出身のフロントエンド開発者です。 UXデザインやチーム開発の効率化など、勉強中です。
SHARE