はじめに
Vue環境で開発をしていると、VueUseはもう欠かせないものになって来ました。
今回は、その中でもブラウザのストレージ(LocalStorageやSessionStorage)を扱うuseStorage
関数に注目し、特にデータのシリアライズ処理について深掘りしていきたいと思います。
使い方
以下のように、keyとなる文字列と、localstorageに保存する値を渡してあげると、毎回setItemやgetItemを書く必要がなくVueのrefのような使い方ができるようになります。
なので、watchをかけたり、値を直で代入することもできます。localStorageは処理が色々なところに散在しがちなのでuseStorageを使うことでかなりスッキリした書き方ができます。
const example = useStorage('key', value);
シリアライザー
シリアライザーとは?
大体のケースは上記の書き方で解決できますが、一つ引っかかることがあります。
localStorageにはオブジェクトをその形のまま保存することができないんです。
値を入れても、[object Object]が保存されてしまいます。
const userData = {
name: "田中",
age: 25,
hobbies: ["読書", "旅行"]
};
localStorage.setItem("user", userData); // '[object Object]'
シリアライザーとは、このようなプログラム上のデータ(オブジェクトや配列など)を保存や転送できる形式(主に文字列)に変換する仕組みのことです。
普段のlocalStorageの使い方だと以下のような形になります。
// シリアライズ(データ → 文字列)
const serialized = JSON.stringify(userData);
localStorage.setItem("user", serialized);
// {"name":"田中","age":25,"hobbies":["読書","旅行"]}
// デシリアライズ(文字列 → データ)
const stored = localStorage.getItem("user");
const deserialized = JSON.parse(stored);
console.log(deserialized.name); // "田中"
useStorage
useStorageでは、以下のようなインターフェースでシリアライザーを定義しています。
export interface Serializer<T> {
read: (raw: string) => T
write: (value: T) => string
}
-
read
: ストレージから読み取った文字列を、適切な型に変換 -
write
: JavaScriptの値をストレージに保存可能な文字列に変換
シリアライザーの実装
VueUseは、以下の型に対する標準的なシリアライザーを提供しています。
- boolean
- number
- string
- object
- map
- set
- date
- any
例えば、booleanのシリアライザーは以下のように実装されてますね。
boolean: {
read: (v: any) => v === 'true',
write: (v: any) => String(v),
}
特に興味深いのは、MapやSetのシリアライザーです。
map: {
read: (v: any) => new Map(JSON.parse(v)),
write: (v: any) => JSON.stringify(Array.from((v as Map<any, any>).entries())),
},
set: {
read: (v: any) => new Set(JSON.parse(v)),
write: (v: any) => JSON.stringify(Array.from(v as Set<any>)),
}
JSON.stringify
では直接シリアライズできない特殊なデータ構造を、配列経由で変換する工夫もされてそうです。Mapの処理ではentries
を使ってたりしてますね。
カスタムシリアライザー
オブジェクトを入れるためだけではなく、enumを使っていて書き込む値と読み取る値を異なったものにしたいときもあり、独自のデータ型を扱う場合は、カスタムシリアライザーを実装することができます。
例えば、以下は座標データを扱うカスタムシリアライザーの例です。
interface Point {
x: number;
y: number;
}
const pointSerializer: Serializer<Point> = {
read: (raw: string) => {
const [x, y] = raw.split(',').map(Number);
return { x, y };
},
write: (value: Point) => `${value.x},${value.y}`
};
// こんな感じでかけます
const position = useStorage('position', { x: 0, y: 0 }, localStorage, {
serializer: pointSerializer
});
おわりに
型安全も、エラーハンドリングも容易にできるようになるので、useStorageのシリアライザーは非常にありがたいですよね。
これからもバンバン使っていきたいところですね~