BACK
Featured image of post localStorage 容量太小?試試 localforage、idb-keyval

localStorage 容量太小?試試 localforage、idb-keyval

localStorage 是前端本地儲存的一種,其容量一般在 5M-10M 左右,用來快取一些簡單的資料基本夠用,畢竟定位也不是大資料量的儲存。在某些場景下 localStorage 的容量就會有點捉襟見肘,其實瀏覽器是有提供大資料量的本地儲存的如 IndexedDB 儲存資料大小一般在 250M 以上。彌補了 localStorage 容量的缺陷,但是使用要比 localStorage 複雜一些的 IndexedDB,不過已經有大佬造了輪子封裝了一些呼叫過程使其使用相對簡單,下面我們一起來看一下

參考網站

localStorage 是前端本地儲存的一種,其容量一般在 5M-10M 左右,用來快取一些簡單的資料基本夠用,畢竟定位也不是大資料量的儲存。

在某些場景下 localStorage 的容量就會有點捉襟見肘,其實瀏覽器是有提供大資料量的本地儲存的如 IndexedDB 儲存資料大小一般在 250M 以上。

彌補了 localStorage 容量的缺陷,但是使用要比 localStorage 複雜一些 mdn IndexedDB

不過已經有大佬造了輪子封裝了一些呼叫過程使其使用相對簡單,下面我們一起來看一下~


localforage

localforage,擁有類似 localStorage API,它能儲存多種型別的資料如 ArrayArrayBufferBlobNumberObjectString,而不僅僅是字串。

這意味著我們可以直接存 物件、陣列型別的資料避免了 JSON.stringify 轉換資料的一些問題。

儲存其他資料型別時需要轉換成上邊對應的型別,比如 vue3 中使用 reactive 定義的資料需要使用 toRaw 轉換成原始資料進行儲存, ref 則直接儲存 xxx.value 資料即可。

安裝

下載最新版本 或使用 npmbower 進行安裝,引入下載的 localforage 即可使用。

  • 通過 npm 安裝:
1
npm install localforage
  • 通過 bower 安裝:
1
bower install localforage

使用

提供了與 localStorage 相同的api,不同的是它是非同步的呼叫返回一個 Promise 物件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
localforage.getItem('somekey')
  .then(function(value) {
    // 當離線倉庫中的值被載入時,此處程式碼執行
    console.log(value); 
  })
  .catch(function(err) {
    // 當出錯時,此處程式碼執行
    console.log(err);
  });

// callback 版本
localforage.getItem('somekey', function(err, value) {
  // 當離線倉庫中的值被載入時,此處程式碼執行
  console.log(value);
});

提供的方法

  • getItem: 根據資料的 key 獲取資料 不存在則返回 null
  • setItem: 根據資料的 key 設定資料(儲存 undefinedgetItem 獲取會返回 null)。
  • removeItem: 根據 key 刪除資料。
  • length: 獲取 key 的數量。
  • key: 根據 key 的索引獲取其名。
  • keys: 獲取資料倉庫中所有的 key
  • iterate: 迭代資料倉庫中的所有 value/key 鍵值對。

配置

完整配置可檢視文件

這裡說個我覺得很有用的:

1
localforage.config({ name: 'My-localStorage' });

設定倉庫的名字,不同的名字代表不同的倉庫,當一個應用需要多個本地倉庫隔離資料的時候就很有用。

1
2
3
4
5
6
const store = localforage.createInstance({ name: "nameHere" });
const otherStore = localforage.createInstance({ name: "otherName" });

// 設定某個資料倉庫 key 的值不會影響到另一個數據倉庫
store.setItem("key", "value");
otherStore.setItem("key", "value2");

同時也支援刪除倉庫

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 呼叫時,若不傳參,將刪除當前例項的 “資料倉庫”。
localforage.dropInstance()
  .then(function() {
    console.log('Dropped the store of the current instance');
  });

// 呼叫時,若引數為一個指定了 name 和 storeName 屬性的物件,會刪除指定的 “資料倉庫”。
localforage.dropInstance({ name: "otherName", storeName: "otherStore" })
  .then(function() {
    console.log('Dropped otherStore');
  });

// 呼叫時,若引數為一個僅指定了 name 屬性的物件,將刪除指定的 “資料庫” (及其所有資料倉庫)。
localforage.dropInstance({ name: "otherName" })
  .then(function() {
    console.log('Dropped otherName database');
  });

idb-keyval

idb-keyval 是用 IndexedDB 實現的一個超級簡單的基於 promise 的鍵值儲存。

安裝

1
npm install idb-keyval

使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 全部引入
import idbKeyval from 'idb-keyval';

idbKeyval.set('hello', 'world')
  .then(() => console.log('It worked!'))
  .catch((err) => console.log('It failed!', err));

// 按需引入會 treeshake
import { get, set } from 'idb-keyval';

set('hello', 'world')
  .then(() => console.log('It worked!'))
  .catch((err) => console.log('It failed!', err));

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

瀏覽器直接引入:

1
<script src="https://cdn.jsdelivr.net/npm/idb-keyval@6/dist/umd.js"></script>

提供的方法

由於其沒有中文的官網,會把例子及自己的理解附上:

set

set: 設定資料

key 可以是 NumberStringDate(IDB 也允許這些值的陣列,但 IE 不支援)。 value 可以是 NumberArrayObjectDateBlobs 等,儘管老 Edge 不支援 null

1
2
3
4
5
import { set } from 'idb-keyval';

set('hello', 'world')
  .then(() => console.log('It worked!'))
  .catch((err) => console.log('It failed!', err));

setMany

setMany: 設定多個數據

一次設定多個值,比一個一個的設定更快

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import { set, setMany } from 'idb-keyval';

// 不應該:
Promise.all([set(123, 456), set('hello', 'world')])
  .then(() => console.log('It worked!'))
  .catch((err) => console.log('It failed!', err));

// 這樣做更快:
setMany([
  [123, 456],
  ['hello', 'world'],
])
  .then(() => console.log('It worked!'))
  .catch((err) => console.log('It failed!', err));

get

get: 獲取資料

如果沒有 key,那麼 value 將返回 undefined

1
2
3
4
5
import { get } from 'idb-keyval';

// logs: "world"
get('hello')
  .then((val) => console.log(val));

getMany

getMany: 獲取多個數據

一次獲取多個數據,比一個一個的獲取資料更快

1
2
3
4
5
6
7
8
9
import { get, getMany } from 'idb-keyval';

// 不應該:
Promise.all([get(123), get('hello')])
  .then(([firstVal, secondVal]) => console.log(firstVal, secondVal));

// 這樣做更快:
getMany([123, 'hello'])
  .then(([firstVal, secondVal]) => console.log(firstVal, secondVal));

del

del: 刪除資料

根據 key 刪除資料

1
2
3
import { del } from 'idb-keyval';

del('hello');

delMany

delMany: 刪除多個數據

一次刪除多個鍵,比一個一個的刪除更快

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
import { del, delMany } from 'idb-keyval';

// 不應該:
Promise
  .all([del(123), del('hello')])
  .then(() => console.log('It worked!'))
  .catch((err) => console.log('It failed!', err));

// 這樣做更快:
delMany([123, 'hello'])
  .then(() => console.log('It worked!'))
  .catch((err) => console.log('It failed!', err));

update

update: 排隊更新資料,防止由於非同步導致資料更新問題

因為 getset 都是非同步的使用他們來更新資料可能會存在問題如:

1
2
3
4
5
6

// Don't do this:
import { get, set } from 'idb-keyval';
get('counter').then((val) => set('counter', (val || 0) + 1); );
get('counter').then((val) => set('counter', (val || 0) + 1); ); 
// 上述程式碼我們期望的是 2 但實際結果是 1,我們可以在第一個 callback 執行第二次操作。

更好的方法是使用 update 來更新資料:

1
2
3
4
5
6
// Instead:
import { update } from 'idb-keyval';

update('counter', (val) => (val || 0) + 1);
update('counter', (val) => (val || 0) + 1);
// 將自動排隊更新,所以第一次更新將計數器設定為 1,第二次更新將其設定為 2。

clear

clear: 清除所有資料

1
2
3
import { clear } from 'idb-keyval';

clear();

entries

entries: 返回 [key, value] 形式的資料

1
2
3
4
import { entries } from 'idb-keyval';

// logs: [[123, 456], ['hello', 'world']]
entries().then((entries) => console.log(entries));

keys

keys: 獲取所有資料的 key

1
2
3
4
import { keys } from 'idb-keyval';

// logs: [123, 'hello']
keys().then((keys) => console.log(keys));

values

values: 獲取所有資料的 value

1
2
3
4
import { values } from 'idb-keyval';

// logs: [456, 'world']
values().then((values) => console.log(values));

createStore

createStore: 自定義倉庫

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
// 自定義資料庫名稱及表名稱
// 建立一個數據庫:

// 資料庫名稱為 tang_shi,表名為 table1
const tang_shi_table1 = idbKeyval.createStore('tang_shi', 'table1');

// 向對應倉庫新增資料
idbKeyval.set('add', 'table1 的資料', tang_shi_table1);

// 預設建立的倉庫名稱為 keyval-store,表名為 keyval
idbKeyval.set('add', '預設的資料');

使用 createStore 建立的資料庫一個庫只會建立一個表:

1
2
3
4
5
6
7
// 同一個庫有不可以有兩個表,custom-store-2 不會建立成功:
const customStore = createStore('custom-db-name', 'custom-store-name');
const customStore2 = createStore('custom-db-name', 'custom-store-2');

// 不同的庫有相同的表名,這是可以的:
const customStore3 = createStore('db3', 'keyval');
const customStore4 = createStore('db4', 'keyval');

總結

本文介紹了兩個 IndexedDB 的庫,用來解決 localStorage 儲存容量太小的問題。

localforageidb-keyval 之間我更喜歡 localforage 因為其與 localStorage 相似的 api 幾乎沒有上手成本。

如果需要更加靈活的庫可以看一下 dexie.jsPouchDBidbJsStore 或者 lovefield 之類的庫。


comments powered by Disqus