前言
自從W3C放棄Web SQL後,Indexed Database成了瀏覽器唯一資料庫部件。要注意的是,這是一套key-value
資料庫系統,和SQL
系統有很大的不同,他反而比較像是python的dict,這點非常重要,我曾在這邊卡了很久。
然而該套API是非同步式API,大量依賴callback,在使用上顯得比較繁瑣,因此網路上陸續出現一些包裝,下面是目前流行的4種indexed db的程式庫的下載排名
老實說在初步翻閱各官方docs後,dexie是裡面最簡單易用的一套,語法相當的淺顯易懂。然而不知為何本篇要介紹的idb
在2019年中忽然使用率大幅攀升,可能是有大神推薦或是有知名套件使用吧。
在使用上,這個套件特色就是使用Promise
將API包奘起來,搭配async/await就能寫出語法易懂的程式,另外它的程式庫大小在壓縮後也是4套中最小(僅1kb),相對的額外功能就比較沒這麼多。完全專住在資料庫最核心功能上
但是當我在找尋相關教學的過程中,發現許多文章都是2018/2019之前,然而該程式庫近期有過大改版,很多語法被破壞。此外也缺乏較完整的使用範例,如果原本對IndexedDB/async/promise就不熟的話,很多地方會容易有誤解。因此才有了這篇文章的誕生
P.S. 注意 Indexed Database API到2015才定稿,因此舊式的瀏覽器(尤其是IE)支援度會有問題
P.S.2 這邊專注在立即上手,若要idb詳細介紹可直接參考官網,API可以參考 中文教學
安裝
寫作當下,該套件為6.0.0版
引入該套件有三種方法
使用npm
npm install idb
從瀏覽器引用 - 用import
1 | <script type="module"> |
從瀏覽器引用 - 傳統引用
1 | <script src="https://unpkg.com/idb/build/iife/index-min.js"></script> |
建立資料庫
OpenDB - 開啟/建立資料庫
在多方探索下,我發現idb可以有幾種寫法
建立資料庫,這部份三種寫法都一樣
1 | // P.S. 這時database接到的是一個promise,故使用上後面要用.then接資料 |
openDB(<資料庫名稱[str]>, <版本[int]>, <新建/升級資料庫callback>)
.upgrade(IDBDatabase, 舊版本[int], 新版本[int], transaction)
開啟資料庫 - 參考
回傳 Promise -> IDBDatabase
IDBDatabase.createObjectStore(<資料表名稱[str]>, <設定項[object] 可省略>)
建立資料表 - 參考
回傳IDBObjectStore
IDBObjectStore.createIndex(<欄位名稱:str>, <索引名稱[str]>, <設定項[object] 可省略>)
建立索引 - 參考
寫法1 - ex:抓取所有資料
1 | // 抓取並顯示所有資料 |
寫法2/3 - ex:抓取所有資料
1 | (async() => { |
寫法4 通通都寫在async函數裡面,適合短暫使用
1 | async function get() { |
以下沒特別說明的話一律使用寫法一
插入/更新資料
由於idb將便捷功能都直接綁定在 IDBDatabase 上(OpenDB回傳的物件),因此很多資料相關動作都能在上面解決。
新增資料 .add()
.add(<資料表名稱[str]>, <資料>, <[object]指定給主鍵的值,可省略>)
注意:如果插入數據的 keyPath 跟資料表內的資料有重複的話會引起失敗
1 | database.then(async(db) => { |
新增/更新資料 .put()
.put(<資料表名稱[str]>, <資料>, <[object]指定給主鍵的值,可省略>)
根據資料內或指定的主鍵找出舊有資料並更新
如果資料內沒有主鍵欄位,或是主鍵資料沒在資料表內找到,會變成插入該筆資料
事務模式
indexeddb支援多筆資料更動,其中一筆失敗也能回復到更動前的狀態
1 | const datas = [...]; |
搜尋/刪除資料
主鍵
抓取資料 .get()
.get(<資料表名稱[str]>, <比對的主鍵值或範圍[str, IDBKeyRange]>)
1 | // 抓取sheet內 id(keyPath) 為 1 的資料 |
抓取全部資料 .getAll()
.getAll(<資料表名稱[str]>, <比對的主鍵值或範圍[str, IDBKeyRange],可省略>, <[int] 抓幾筆資料,可省略>)
1 | // 抓取 sheet 內所有的資料 |
抓取所有key值 .getAllKeys()
1 | // 抓取 sheet 內每筆資料的keyPath |
P.S.其實還有抓取key值得getKey(key),但是你都知道key值了還抓個鳥…
取得資料總筆數 .count()
1 | database.then(async(db) => { |
刪除資料 .delete()
.delete(<資料表名稱[str]>, <比對的主鍵值或範圍[str, IDBKeyRange],可省略>)
1 | // 刪除sheet內 id(keyPath) 為 1 的資料 |
清空資料表內所有資料 .clear()
.clear(<資料表名稱[str]>)
1 | database.then(async(db) => { |
index
看到這邊可以注意到,似乎在搜尋上只能使用一開始設定的主鍵keyPath
如果要針對別的欄位做比對搜尋,就得再建立資料庫的時候,對日後預計會使用到的欄位建立indexstore.createIndex( <索引名稱[str]>, <欄位路徑[str]>, {uniqle[bool]是否允許index重複-可省略, multiEntry[bool] 是否支援陣列內索引-可省略})
這邊稍微介紹一下:
假設資料結構是:
1 | { |
如果要針對chinese來下index可以用這個語法store.createIndex("chinese", "score.chinese")
如果要針對陣列內所有的值可以用以下語法store.createIndex("girlfriends", "girlfriends", {multiEntry:true})
範圍搜尋
indexeddb的搜尋是依賴 IDBKeyRange 物件,該物件除了index外野適用於keyPath,一共有4種用法:
- IDBKeyRange.lowerBound(x, [bool]是否包含x ):指定下限。
- IDBKeyRange.upperBound(x, [bool]是否包含x ):指定上限。
- IDBKeyRange.bound(x, y, [bool]是否包含x, [bool]是否包含y):同时指定上下限。
- IDBKeyRange.only():指定只包含一个值。
1 | // 搜尋chinese分數大於50的同學 |
以上是大致上的運用,後續會在談到其他比較進階的功能,下回見~