タイプセーフなStorage API管理 + Svelte
導入
LocalStorageやSessionStorageといったStorage APIは非常に便利な存在ですが、数値やオブジェクトを格納しようとすると型定義が曖昧になりがちです。
また、いちいちシリアライズ、デシリアライズを噛ませるのもネックになります。
そこで今回は、Storage APIをより便利に扱うラッパーライブラリを作成したので紹介します。
@jill64/typed-storage
string型
まずは、型指定なし(string
型)から説明します。
typedStorage
にキーを渡すとゲッターとセッターを作ってくれます。
import { typedStorage } from '@jill64/typed-storage'
const store = typedStorage('local-storage-key')
store.get()
store.set('foo')
Storage APIは変更をサブスクライブできる機能もついているので、イベントリスナーもラップできます。
import { typedStorage } from '@jill64/typed-storage'
const store = typedStorage('local-storage-key')
const unsubscriber = store.addListener((value) => {
// 'local-storage-key'の値が変更されるとconsoleに出力
console.log(value)
})
// サブスクライブを止める
unsubscriber()
カスタム型
typedStorage
の第二引数にserde
パラメータを渡すことで、string
型以外を扱うことができます。serde
パラメーターはseriarize
とdeserialize
メソッドで構成されていて、それぞれの型に応じて定義します。
例えば、number
型を扱いたい場合、serde
の簡単な実装は以下のようになります。
const number = {
serialize: (value: number) => value.toString(),
desirialize: (value: string) => Number(value)
}
これを使うと以下のようなことができます。
import { typedStorage } from '@jill64/typed-storage'
const store = typedStorage('local-storage-num', {
serialize: (value: number) => value.toString(),
desirialize: (value: string) => Number(value)
})
// number型
store.get()
store.set(123)
よく使用されるserde
はあらかじめライブラリに準備されています。
import { typedStorage } from '@jill64/typed-storage'
import { number } from '@jill64/typed-storage/serde'
const store = typedStorage('local-storage-num', number)
これ以外にもenum
やjson
など、準備された様々なserde
があります。
詳細はts-serdeをご覧ください。
(また記事にするかもしれないです)
追記(2025/01/14)
SessionStorage
LocalStorageの代わりにSessionStorageを使用することもできます。
第3引数にオプションを渡すだけです。
import { typedStorage } from '@jill64/typed-storage'
import { number } from '@jill64/typed-storage/serde'
const store = typedStorage('local-storage-num', number, {
sessionStorage: true
})
Svelteでもっと便利に扱いたい!
私はSvelteが好きなのでさらにラップするライブラリを作成しました。
こちらはSvelte5からの仕様でAPIが若干異なっています。
import { storage } from '@jill64/svelte-storage'
import { string, number, boolean } from '@jill64/svelte-storage/serde'
const localStorage = storage({
['localStorage-key-str']: string,
['localStorage-key-num']: number,
['localStorage-key-bool']: boolean
})
consol.log(localStorage['localStorage-key-str'])
consol.log(localStorage['localStorage-key-num'])
consol.log(localStorage['localStorage-key-bool'])
localStorage['localStorage-str'] = 'value'
localStorage['localStorage-num'] = 123
localStorage['localStorage-bool'] = true
第1引数はキーとserde
のオブジェクトで構成されます。
この時localStorage
が冗長に感じますが、デストラクチャリングをしてはいけません。
なぜならSvelte5のリアクティビティは、値ではなくゲッター経由でないと得られないからです。
import { storage } from '@jill64/svelte-storage'
import { string, number, boolean } from '@jill64/svelte-storage/serde'
const { str, num, bool } = storage({
str: string,
num: number,
bool: boolean
})
// リアクティブではない
consol.log(str)
consol.log(num)
consol.log(bool)
必要ならば変数名を短くして対応しましょう。
なお、同じくSessionStorageも使えます。
import { storage } from '@jill64/svelte-storage'
import { string } from '@jill64/svelte-storage/serde'
const storage = storage(
{ ['localStorage-key']: string },
{ sessionStorage: true }
)
まとめ
いかがだったでしょうか。
この記事がタイプセーフなStorage API利用の助けになれば幸いです。
また上記ライブラリ群についてバグや不明点がありましたらぜひ各リポジトリからIssueを開いてください。
Discussion