使用 IndexedDB 實現 Client 端數據存儲 – 前端工程師必學的 Web Storage 技術（第三篇）
Tags: javascript, webdev, indexeddb, tutorial
Published: 2022-12-29 13:12:15

## 本篇要解決的問題

之前寫過存在瀏覽器上的二種方式：[Cookies](https://www.letswrite.tw/client-storage-cookie/)、[LocalStorage](https://www.letswrite.tw/client-storage-local/)。本篇要寫的是第三種方式：IndexedDB，主要會寫的是推薦套件的使用，IndexedDB 介紹或原理不會寫，因為找資料時有看到幾篇寫的很詳細的教學文了~~（絕對不會說是太懶）~~。

本篇推薦的套件是：[IDB-Keyval](https://github.com/jakearchibald/idb-keyval)，簡單好用，檔案大小甚至不到 1KB，真的是居家旅行必備良藥。

推薦參考文章：

- [Working with IndexedDB](https://web.dev/indexeddb/)
- [瀏覽器數據庫IndexedDB 入門教程](https://www.ruanyifeng.com/blog/2018/07/indexeddb.html)

最後完成的 Demo 會在這：

<https://letswritetw.github.io/letswrite-client-storage/>

---

## 限制

- 限制大小：約 250 MB (可檢測，詳見下段)
- 限制期限：要主動刪除或程式刪除
- 資料格式：除了純值，也可存 Array、Object、Date、Blob 等

---

## 檢測瀏覽器儲存空間大小

在開始使用 IDB 之前，先提供一個 function，可以知道瀏覽器提供的儲存空間有多少：

```js
navigator.storage.estimate().then((estimate) => {
  console.log('總配額', Math.round(estimate.quota * 0.000001) + ' MB')
  console.log('已使用', Math.round(estimate.usage * 0.000001) + ' MB');
});
```

如果懶得寫程式，瀏覽器上如 Chrome、Edge 上也可以直接看到。

在隨便一個網頁上點右鍵按「檢查」，出現的面版上，選單點擊「應用程式」，再選擇「儲存空間」就可以看到了，畫面會像這樣：

![Chrome 儲存空間](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qh8ep3g8uzwm9fe3h4n7.png)

![Edge 儲存空間](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ks6bj7yzt5237cg5q8pi.png)

---

## 安裝 IDB-Keyval

安裝方式很簡單，就跟一般我們要引用 JS 套件一樣。

### npm

```html
$ npm install idb-keyval
// 或
$ yarn add idb-keyval
```

```js
import { get, set } from 'idb-keyval';
```

### CDN

```html
<script type="module">
  import { get, set } from 'https://cdn.jsdelivr.net/npm/idb-keyval@6/dist/index.min.js';
</script>
```

---

## 建立資料 set、setMany

`set` 用在一次儲存一筆資料，`setMany` 用在一次要儲存多筆資料。

值接受的格式，文件上是說都支援，像是 Number、Array、Object、Date、Blob 等等。

只是遇到上古神獸的話（如 IE），有些值會不支援，但都是上古神獸了，就讓它存在我們的回憶裡吧。

```js
import { set, setMany } from 'idb-keyval';

// set
set('String', 'Lets Write')
  .then(() => console.log('set 儲存成功'))
  .catch((err) => console.log('set 儲存失敗', err));

// setMany
setMany([
  ['Boolean', true],
  ['Number', 1234567890],
  ['Date', new Date()],
  ['Object', { 'Author': 'August' }],
  ['Array', [1, 2, 3]]
]).then(() => console.log('setMany 儲存成功'))
  .catch((err) => console.log('setMany 儲存失敗', err));
```

可以看到存進去的資料如下：

![儲存資料](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1fcpbhm46zhyuliew0e2.png)

---

## 抓取資料 get、getMany、entries、keys、values

`get` 是取一筆，`getMany` 是取多筆。

```js
import { get, getMany } from 'idb-keyval';

// get
get('String').then((val) => console.log(val));

// getMany
getMany(['Boolean', 'Date', 'Object']).then(([v1, v2, v3]) => {
  console.log(v1);
  console.log(v2);
  console.log(v3);
});
```

`entries` 是取得資料表裡的所有資料，返回的值會是陣列：`[key, value]`。

```js
import { entries } from 'idb-keyval';

entries().then((entries) => console.log(entries));
// => [["Boolean", true], ["String", "Lets Write"], ... ]
```

`keys` 是取得所有的 key，`values` 是取得所有的 value。

```js
import { keys, values} from 'idb-keyval';

// keys
keys().then((keys) => console.log(keys));
// => ["Array", "Boolean", "Date", "Number", "Object", "String"]

// values
values().then((values) => console.log(values));
// => [[1, 2, 3], true, "2022-12-27T20:10:05.521Z", 1234567890, {"Author": "August" }, "Lets Write"]
```

---

## 更新資料 update

update 同時包含了 set、get，它的主要使用情境是：不確定 IDB 有沒有存某個值，有的話就更新，沒有的話就不理。

如果沒有 update，我們就得先用 get 確認資料是否存在，接著再用 set 去覆寫資料。

有了 update，我們就可以寫成以下：

```js
import { update } from 'idb-keyval';

update('Array', function(val){
  console.log(val);
  return [4, 5, 6]
}).then(() => console.log('update 成功'))
  .catch((err) => console.log('update 失敗', err));
```

`console.log(val)` 可以看到原本的值，`return` 則是寫入新值。

---

## 刪除資料 del、delMany、clear

`del` 是刪一筆，`delMany` 是刪多筆，`clear` 是把資料表整個清空。

```js
// del
del('String')
  .then(() => console.log('del 刪除成功'));

// delMany
delMany(['Date', 'Object'])
  .then(() => console.log('delMany 成功'))
  .catch((err) => console.log('delMany 失敗', err));

// clear
clear();
```

---

## 客製資料庫名稱 createStore

如果沒有指定資料庫的名稱，預設的資料庫會是「keyval-store > keyval」。

用 `createStore` 就可以為資料庫取名稱。

```js
import { createStore } from 'idb-keyval';

const customStore = createStore('客製 DB 名稱', '客製 store 名稱');
```

用了 createStore 後，在執行讀寫資料時，就要多指定是要存進哪個資料庫裡。

```js
import { createStore, set } from 'idb-keyval';

const CustomIDB = createStore('LetsWrite', 'GoodStore');

// set
set('String-Custom', 'Lets Write Happy New Year~', CustomIDB);

// get
get('String-Custom', CustomIDB).then((val) => console.log(val));

// entries
entries(CustomIDB).then((entries) => console.log('CustomIDB entries', entries));
```

---

## Demo 及原始碼

Demo 及原始碼都放在 GitHub 上了，取用之前麻煩分享本篇或在 GitHub 上按個星星，你的小小動作對本站都是大大的鼓勵。

Demo：<https://letswritetw.github.io/letswrite-client-storage/>

原始碼：<https://github.com/letswritetw/letswrite-client-storage>

---

## 瀏覽器上的儲存空間系列

- [Cookies](https://www.letswrite.tw/client-storage-cookie/)
- [sessionStorage、localStorage](https://www.letswrite.tw/client-storage-local/)
- [IndexedDB](https://www.letswrite.tw/client-storage-idb/)