chrome extension StorageArea の仕様
微妙に分かりにくいので、調べた。🧐🧐🧐
chrome.storage - Chrome Developers
StorageArea
はじめに
Chromeバージョン: 104.0.5112.81(Official Build) (64 ビット)
@types/chrome
でいうと、バージョン 0.0.193
全て?のメソッドが Promise 実装になっていた。🙌
storage は大きく local
と sync
がある。
-
sync
: クラウドに保存され、アカウント内で共有される。- 容量制限厳し目、キー数も上限あり(512)
-
local
: ローカルで沢山保存するものはlocal
に保存する。-
unlimitedStorage
権限を渡せば、いくらでも保存できる。
-
なので、設定情報は sync
、通常使用の情報は local
が良いだろう。
使い方は同じ。
(その他の情報は、最上部リンクか、他記事のほうがきっと詳しい)
(見逃している機能があったら失礼&教えて下さい🙇)
基本
set()
await chrome.storage.local.set({ a: 1 })
await chrome.storage.local.get()
> { a: 1 }
await chrome.storage.local.set({ b: 1 })
await chrome.storage.local.get()
> { a: 1, b: 1 }
await chrome.storage.local.set({ b: 2, c: 2 })
await chrome.storage.local.get()
> { a: 1, b: 2, c: 2 }
get()
の仕様はオブジェクトのマージ。
気にせず突っ込めば、そのまま上書きしてくれる。
get()
await chrome.storage.local.get()
> { a: 1, b: 2, c: 2 }
await chrome.storage.local.get('a')
> { a: 1 }
await chrome.storage.local.get(['b'])
> { b: 2 }
await chrome.storage.local.get(['a', 'd'])
> { a: 1, b: 2 }
await chrome.storage.local.get(['d'])
> {}
set()
は空なら全て、指定すればそのキーが存在したら取ってくる。
単体でも無くても、必ずオブジェクトが帰ってくるので注意。
key の順番は保証されない。(そらオブジェクトなんで)
?.
とか in
使って存在確認しながら使うのが吉。
delete()
await chrome.storage.local.get()
> { a: 1, b: 2, c: 2 }
await chrome.storage.local.remove('c')
await chrome.storage.local.get()
> { a: 1, b: 2 }
await chrome.storage.local.remove(['a', 'd'])
await chrome.storage.local.get()
> { b: 2 }
await chrome.storage.local.set({ a: 1, b: 2, c: 2 })
await chrome.storage.local.clear()
await chrome.storage.local.get()
> {}
指定の方法は get()
と同様。
データが無くても何も起きない。
全消ししたい場合は clear()
を使う。
getBytesInUse()
await chrome.storage.local.set({ a: 1, b: 2, c: 2 })
await chrome.storage.local.getBytesInUse()
> 6
await chrome.storage.local.clear()
await chrome.storage.local.getBytesInUse()
> 0
この文字列で 6byte
らしい。
他アプリでMB単位になってもそんなに重くないんで、かなり優秀。(SSD)
挿入可能な値
Primitive values such as numbers will serialize as expected. Values with a typeof "object" and "function" will typically serialize to {}, with the exception of Array (serializes as expected), Date, and Regex (serialize using their String representation).
Deeplさん
数値のような原始的な値は、期待通りにシリアライズされます。typeof "object" と "function" を持つ値は、Array(期待どおりにシリアライズされます)、Date、Regex(String表現でシリアライズされます)を除いて、通常{}にシリアライズされます。
とのこと。
Date
でも Dateを入れると {}
になってしまう。🤔
まぁ ISO8601 や ISO9075 で保存すれば良いかなと。
await chrome.storage.local.set({ date: new Date() })
await chrome.storage.local.get()
> { date:{} }
Array
Array を入れると想定通り挿入される。
これは使い勝手が良さそう。
await chrome.storage.local.set({ items: [1, 2, 3] })
await chrome.storage.local.get()
> { items: [1, 2, 3] }
応用
特定のキー名の item を取得する
s
から始まるものを取得する。
await chrome.storage.local.set({
s000: 'アイテムS0',
a000: 'A000',
s001: 'アイテムS1',
})
const bucket = await chrome.storage.local.get()
Object.entries(bucket)
> [['a000', 'A000'], ['s000', 'アイテムS0'], ['s001', 'アイテムS1']]
Object.entries(items).filter(([key]) => key.startsWith('s'))
> [['s000', 'アイテムS0'], ['s001', 'アイテムS1']]
Object.fromEntries(
Object.entries(items).filter(([key]) => key.startsWith('s'))
)
> { s000: 'アイテムS0', s001: 'アイテムS1' }
Object.entries()
と Object.fromEntries()
で object -> array -> object とするよく見る感じ。
キーは分割代入で取り出し、そのまま配列操作。
object じゃなくて良いなら、fromEntries()
は省略できる。
get にフィルター機能は無いので、全件取得してフィルターをかける。
startsWith
とか includes
とか match
とか使えば、どんな条件でも取り出せるはず。
カーソルみたいな機能があればなぁ...と。
特定のキー名の item を削除する
s
から始まるものを消す。
await chrome.storage.local.set({
s000: 'アイテムS0',
a000: 'A000',
s001: 'アイテムS1',
})
const bucket = await chrome.storage.local.get()
Object.keys(bucket)
> ['a000', 's000', 's001']
Object.keys(bucket).filter((key) => key.startsWith('s'))
> ['s000', 's001']
await chrome.storage.local.remove(
Object.keys(bucket).filter((key) => key.startsWith('s'))
)
await chrome.storage.local.get()
> ['a000']
Object.keys()
でキーを取り出し、そのまま配列操作。
storage 参照に配列を使うことができるので、お手軽実装となる。
データ構造考察
要するに key-value ストレージなので、どの粒度で保存するか考えるべし。
config を保存する場合、いくつかパターンが思いつく。
- ① キーごとに保存
- color: 'blue'
- showMessage: false
- screenName: 'foo'
- ② config というオブジェクトを保存
- config: { color: 'blue', 'showMessage': false, screenName: 'foo' }
post - message という構造の場合こんな感じ。
- ① post, message それぞれ別の採番を行って保存
- post000: Post
- post001: Post
- ...
- message000: Message
- message001: Message
- ...
- (①の派生) post - messages のオブジェクトを作って保存する
- 000: Post & { messages: Messages[] }
- 001: Post & { messages: Messages[] }
- ...
- ② post, message それぞれを配列として保存する
- posts: Post[]
- messages: Message[]
実装が簡単なのは①、情報のまとまりがきれいなのが②かな。
value 側の要素が大きくなりすぎると、取得時のコストが上がる気がする。
このあたりの最適解は分からないねぇ。。。
まぁ、②で実装したプログラムも、なんの遅延も無く動いているので、現代のPCじゃあんまり気にすることじゃないのかもしれない。
- key1つに対して、紐づくvalueの塊を取り出す。
- 検索機能はなく、全てを走査する必要がある。
というのを前提に、よく呼び出すものをkeyに置けるといいな、の精神で構築するのが良いと思っている。
キーが被らない設計のほうが大事。(1敗)
Discussion