TypeScriptを使用してlocalStorage
やsessionStorage
を操作する場合、通常は文字列データを格納・取得するシンプルなAPIが提供されています。しかし、これらのAPIは基本的にJavaScriptに依存しており、型安全性が保証されていません。型の違いやデータの不一致が原因で、実行時エラーやバグの原因となることもあります。特に大規模なプロジェクトや複雑なデータ構造を扱う際には、型安全に操作することが非常に重要です。本記事では、TypeScriptを活用してlocalStorage
とsessionStorage
を型安全に操作する方法について詳しく解説します。
`localStorage`と`sessionStorage`の基礎
localStorage
とsessionStorage
は、どちらもクライアントサイドのWebストレージAPIを利用してデータを保存するための仕組みですが、その特性にはいくつかの違いがあります。
`localStorage`とは
localStorage
は、ブラウザに永続的にデータを保存できる仕組みです。ページをリロードしたり、ブラウザを閉じてもデータは保持されます。そのため、長期的にデータを保持したい場合に便利です。
特徴
- データの永続性:ブラウザを閉じてもデータが残ります。
- 容量制限:通常5MB程度(ブラウザによって異なります)。
- 同一オリジン(ドメイン)間でデータを共有可能。
`sessionStorage`とは
一方で、sessionStorage
はセッション単位でデータを保持します。ブラウザやタブを閉じるとデータが削除され、セッションの終了とともに消えるため、一時的なデータの保存に適しています。
特徴
- セッション単位のデータ保持:タブやウィンドウを閉じるとデータが削除されます。
- 容量制限:
localStorage
と同じく、ブラウザによって異なりますが通常5MB程度。 - 同じタブやウィンドウ内でのみデータが有効。
このように、用途に応じてlocalStorage
とsessionStorage
を使い分けることが重要です。次に、型安全でない操作のリスクについて説明します。
型安全でない操作のリスク
localStorage
やsessionStorage
のAPIは非常にシンプルで、setItem
メソッドでデータを保存し、getItem
メソッドでデータを取得します。しかし、これらのAPIは全てのデータを文字列として保存するため、データ型の保証がされません。型安全でない操作を行うことで、次のようなリスクが生じます。
データの不一致によるエラー
localStorage
やsessionStorage
は、データを文字列として保存するため、数値やオブジェクトなどをそのまま保存することはできません。これにより、保存時や取得時に文字列に変換したり、文字列から元の型に変換する必要があります。この変換処理でエラーが発生することがあり、例えば、JSON.parse
によるデータ変換が失敗すると、コードがクラッシュする原因となります。
例: 数値データの不一致
localStorage.setItem("count", "10"); // 保存されるのは文字列"10"
const count = parseInt(localStorage.getItem("count") as string, 10); // 型変換が必要
このように、保存されたデータが文字列であるため、取り出した後に数値に変換する必要があります。しかし、型安全でないと、この変換が失敗したり、意図しないデータ型のまま操作してしまうリスクがあります。
実行時エラーの原因
取得するデータが想定していた型ではない場合、実行時にエラーが発生する可能性があります。例えば、オブジェクトをJSON.stringify
で保存し、取り出す際にJSON.parse
を用いるケースで、データが破損しているときにパースエラーが発生することがあります。このようなケースでは、データが完全に失われたり、アプリケーションの動作が停止することも考えられます。
例: JSONパースエラー
localStorage.setItem("user", JSON.stringify({ name: "Alice" }));
const user = JSON.parse(localStorage.getItem("user") as string); // ここでパースエラーの可能性
このように、データ型が明確に定義されていない状態で操作すると、実行時のエラーや予期しない挙動が発生するリスクが高まります。次に、TypeScriptを活用してどのように型安全にするか、その方法を解説していきます。
TypeScriptで型安全にするための基本的な考え方
localStorage
やsessionStorage
を型安全に操作するためには、TypeScriptの型システムを最大限に活用することが重要です。データの保存・取得時に型を明示し、意図しないデータ型の不一致や変換ミスを防ぐことができます。ここでは、TypeScriptの型定義を用いて、localStorage
やsessionStorage
を型安全に操作するための基本的な考え方を説明します。
文字列だけでなく、型を明示する
TypeScriptでは、保存するデータの型を事前に定義することで、データの不一致によるエラーを防ぐことができます。例えば、localStorage
に保存するデータが数値なのか、オブジェクトなのか、文字列なのかを明示することが大切です。型を明示することで、コンパイル時に型の整合性を確認でき、ランタイムエラーを回避できます。
例: 型定義の使用
type User = {
name: string;
age: number;
};
// ユーザーデータを保存する際に、型を定義して使用
const user: User = { name: "Alice", age: 25 };
localStorage.setItem("user", JSON.stringify(user)); // 型安全な保存
// データ取得時にも型を活用
const storedUser = JSON.parse(localStorage.getItem("user") as string) as User;
console.log(storedUser.name); // 正しく型が反映されている
このように、保存時と取得時の両方で型をしっかり定義しておくことで、コードの可読性が向上し、実行時に予期しないデータ型のエラーを回避できます。
TypeScriptの`as`構文での型キャスト
localStorage
やsessionStorage
からデータを取得する際には、常に文字列が返されます。そのため、TypeScriptで扱う型にキャストする必要があります。このとき、as
構文を利用して、取得したデータを明示的に期待する型にキャストすることで型安全を確保できます。
例: 型キャストを用いた操作
const item = localStorage.getItem("key");
if (item !== null) {
const data = JSON.parse(item) as MyDataType; // 型キャストを利用してデータを型安全に取得
}
as
を使ったキャストは、データが期待通りであることを明示するため、開発者にとってもコードの信頼性を高める手段です。
型安全な関数の設計
さらに、よく使うストレージ操作を関数化し、型を活用して再利用性を高めることも可能です。これにより、プロジェクト全体で一貫した型安全な操作が実現できます。例えば、データの保存や取得をラップするユーティリティ関数を設計し、型定義を適用することで、使い回しが容易になり、コードのメンテナンス性も向上します。
次に、これを実現するための具体的なユーティリティ関数の実装例を見ていきます。
ユーティリティ関数を用いた型安全な操作
TypeScriptでlocalStorage
やsessionStorage
を型安全に操作するために、ユーティリティ関数を作成することで、コードの再利用性やメンテナンス性が大幅に向上します。ここでは、型を安全に扱うための基本的なユーティリティ関数の実装例を紹介します。
型安全な`setItem`関数
まず、localStorage
やsessionStorage
にデータを保存する際に、型を保証するための関数を作成します。この関数は、保存するデータをJSONに変換し、エラーが発生しないようにします。
実装例
function setItem<T>(key: string, value: T): void {
localStorage.setItem(key, JSON.stringify(value));
}
この関数では、ジェネリクスを使って型を動的に受け取り、あらゆるデータ型に対応できるようにしています。value
はどのような型でも受け取れますが、内部ではJSON.stringify
によって文字列に変換され、localStorage
に保存されます。
型安全な`getItem`関数
次に、データを取得するための型安全な関数を実装します。取得時にもデータの型が保証されるように、保存されたデータを取り出す際にJSONをパースし、指定した型に変換します。
実装例
function getItem<T>(key: string): T | null {
const item = localStorage.getItem(key);
if (!item) return null;
return JSON.parse(item) as T;
}
このgetItem
関数は、引数として指定した型T
にキャストした上でデータを返します。localStorage
にデータが存在しない場合はnull
を返すため、型安全なコードを維持しつつエラーハンドリングもできます。
型安全な`removeItem`関数
不要になったデータを削除するためのremoveItem
関数もシンプルに型安全に実装できます。
実装例
function removeItem(key: string): void {
localStorage.removeItem(key);
}
このremoveItem
関数は、指定されたキーのデータをlocalStorage
から削除するだけのシンプルな処理です。型に関わる部分はありませんが、コードの一貫性を保つため、型安全な関数として扱います。
一連の操作を統合したクラスの実装
これらの関数をまとめて、統一したAPIを提供するクラスを作成することもできます。クラスを使うことで、localStorage
やsessionStorage
へのアクセスをより一元的に管理できます。
実装例
class StorageService {
static setItem<T>(key: string, value: T): void {
localStorage.setItem(key, JSON.stringify(value));
}
static getItem<T>(key: string): T | null {
const item = localStorage.getItem(key);
if (!item) return null;
return JSON.parse(item) as T;
}
static removeItem(key: string): void {
localStorage.removeItem(key);
}
}
このStorageService
クラスを使えば、プロジェクト内でlocalStorage
やsessionStorage
の操作を簡潔かつ型安全に行えます。例えば、以下のように使用します。
使用例
// ユーザーデータを保存
StorageService.setItem<User>("user", { name: "Alice", age: 25 });
// ユーザーデータを取得
const user = StorageService.getItem<User>("user");
console.log(user?.name);
このように、ユーティリティ関数を用いることで、型安全かつ効率的にデータを操作することが可能になります。次に、ジェネリクスを使ったより汎用的な解決策について詳しく見ていきます。
Genericsを使った汎用的な解決策
Generics(総称型)を活用することで、localStorage
やsessionStorage
に保存・取得するデータの型を柔軟に扱うことが可能です。Genericsを使用すると、特定の型に依存しない汎用的な関数やクラスを作成でき、様々なデータ型に対応した型安全なコードを効率的に実装できます。
Genericsとは
Genericsは、TypeScriptの特徴の一つであり、関数やクラス、インターフェースに型パラメータを渡すことで、型を柔軟に定義できる仕組みです。これにより、特定の型に縛られることなく、さまざまなデータ型に対応する汎用的なロジックを構築できます。
Genericsの基本的な使い方
Genericsを使うことで、データの型に依存しない関数を作成できます。以下は、データの型を指定できる汎用的なsetItem
とgetItem
関数の実装例です。
function setItem<T>(key: string, value: T): void {
localStorage.setItem(key, JSON.stringify(value));
}
function getItem<T>(key: string): T | null {
const item = localStorage.getItem(key);
if (!item) return null;
return JSON.parse(item) as T;
}
このT
は、型パラメータであり、関数が呼び出されるときに具体的な型が割り当てられます。これにより、localStorage
の保存・取得がどんな型であっても型安全に行えるようになります。
Genericsを使った複雑なデータの処理
Genericsの強みは、単純な型だけでなく、オブジェクトや配列といった複雑なデータ構造に対しても型安全な処理を適用できる点です。たとえば、ユーザーのリストや設定オブジェクトをlocalStorage
に保存・取得する際にも、Genericsを活用することで柔軟かつ型安全な操作が可能になります。
複雑なオブジェクトの保存・取得例
type User = {
id: number;
name: string;
};
type Settings = {
theme: string;
notifications: boolean;
};
// ユーザーリストを保存
const users: User[] = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" }
];
setItem<User[]>("users", users);
// 設定を保存
const settings: Settings = { theme: "dark", notifications: true };
setItem<Settings>("settings", settings);
// データを取得
const storedUsers = getItem<User[]>("users");
const storedSettings = getItem<Settings>("settings");
console.log(storedUsers);
console.log(storedSettings);
このように、Genericsを利用することで、どんなデータ型でも安全にlocalStorage
やsessionStorage
に保存・取得することができます。また、データの型が異なる場合でも、関数を使い回すことができるため、重複したコードを減らし、保守性が向上します。
複数のストレージ操作を統合するクラス設計
Genericsを活用したクラスを作成し、localStorage
やsessionStorage
に対する操作を統一的に管理できるようにすることもできます。このアプローチにより、ストレージの操作がより汎用的で堅牢になります。
Genericsを用いたクラス設計の例
class StorageManager<T> {
private storage: Storage;
constructor(storage: Storage) {
this.storage = storage;
}
setItem(key: string, value: T): void {
this.storage.setItem(key, JSON.stringify(value));
}
getItem(key: string): T | null {
const item = this.storage.getItem(key);
if (!item) return null;
return JSON.parse(item) as T;
}
removeItem(key: string): void {
this.storage.removeItem(key);
}
}
このStorageManager
クラスでは、localStorage
とsessionStorage
のどちらも扱えるように、コンストラクタでストレージを指定しています。また、Genericsを使用して型安全な操作ができるため、任意のデータ型に対応可能です。
使用例
const localStorageManager = new StorageManager<User[]>(localStorage);
const sessionStorageManager = new StorageManager<Settings>(sessionStorage);
// データを保存
localStorageManager.setItem("users", users);
sessionStorageManager.setItem("settings", settings);
// データを取得
const storedUsers = localStorageManager.getItem("users");
const storedSettings = sessionStorageManager.getItem("settings");
このように、Genericsを活用することで、コードの柔軟性と型安全性を両立させた汎用的なストレージ操作が実現できます。
次に、これらの操作に対するエラーハンドリングのベストプラクティスについて見ていきます。
エラーハンドリングのベストプラクティス
localStorage
やsessionStorage
を使用する際、エラーハンドリングをしっかり行うことは、アプリケーションの安定性やデータの整合性を保つために非常に重要です。ストレージ操作には、ブラウザの制限やデータの破損など、様々なエラーが発生する可能性があるため、それらに適切に対応する方法を身につける必要があります。ここでは、エラーハンドリングのベストプラクティスをいくつか紹介します。
例外処理を使ったエラーハンドリング
localStorage
やsessionStorage
を操作する際、最も基本的なエラーハンドリングの方法は、try-catch
を使って例外をキャッチすることです。特に、JSON.parse
やJSON.stringify
によるデータ変換時にエラーが発生する可能性があるため、これらの処理には例外処理を組み込むことが推奨されます。
例: 例外処理の使用
function safeGetItem<T>(key: string): T | null {
try {
const item = localStorage.getItem(key);
if (!item) return null;
return JSON.parse(item) as T;
} catch (error) {
console.error(`Error parsing JSON for key "${key}":`, error);
return null;
}
}
function safeSetItem<T>(key: string, value: T): void {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
console.error(`Error saving to localStorage for key "${key}":`, error);
}
}
このように、try-catch
ブロックを使用することで、ストレージ操作中に発生する可能性のある例外を捕捉し、エラーメッセージをログに出力することができます。これにより、エラー発生時にアプリケーションがクラッシュすることを防ぎ、代わりに安全にエラー処理を行えます。
ストレージ容量オーバーの対応
localStorage
やsessionStorage
には、通常5MB前後の容量制限が設定されています。この制限を超えてデータを保存しようとすると、QUOTA_EXCEEDED_ERR
と呼ばれるエラーが発生することがあります。このエラーに対する対処を行わないと、データが正しく保存されず、アプリケーションが動作不良を引き起こす可能性があります。
例: 容量オーバーエラーの対処
function safeSetItemWithQuotaCheck<T>(key: string, value: T): void {
try {
localStorage.setItem(key, JSON.stringify(value));
} catch (error) {
if (error instanceof DOMException && error.code === 22) {
console.error("LocalStorage quota exceeded.");
} else {
console.error(`Error setting item "${key}" to localStorage:`, error);
}
}
}
この実装では、容量オーバーのエラーを検知し、適切にログ出力することで、ユーザーにエラーの原因を知らせることができます。
データの不整合に対する対策
localStorage
やsessionStorage
に保存されたデータが破損している場合や、予期しない形式で保存されている場合、パースエラーやデータの不整合が発生することがあります。こうした問題に対処するためには、保存されているデータの形式を常に確認し、データが正しいことを前提に処理しないことが重要です。
例: データのバリデーション
function validateUserData(data: any): data is User {
return typeof data === "object" && "name" in data && "age" in data;
}
function safeGetUser(): User | null {
try {
const item = localStorage.getItem("user");
if (!item) return null;
const parsed = JSON.parse(item);
if (validateUserData(parsed)) {
return parsed;
} else {
console.error("Invalid user data format");
return null;
}
} catch (error) {
console.error("Error retrieving user data:", error);
return null;
}
}
この例では、データを取得した後、データが正しい形式であるかどうかをバリデーション関数で確認しています。これにより、不整合なデータをアプリケーション内で使用することを防ぎ、バグやクラッシュを回避できます。
デフォルト値の使用
データの取得が失敗した場合や、保存されているデータが存在しない場合に、デフォルト値を使用することは、エラーハンドリングの一環として有効です。デフォルト値を設定しておけば、データが見つからない場合でも安全に操作を続けることができます。
例: デフォルト値の適用
function getItemWithDefault<T>(key: string, defaultValue: T): T {
try {
const item = localStorage.getItem(key);
if (!item) return defaultValue;
return JSON.parse(item) as T;
} catch (error) {
console.error(`Error retrieving item "${key}" from localStorage:`, error);
return defaultValue;
}
}
この関数では、データが見つからなかったり、取得中にエラーが発生した場合に、指定されたデフォルト値を返します。これにより、予期しないエラーが発生しても、アプリケーションは動作を継続できるようになります。
まとめ
エラーハンドリングは、アプリケーションの信頼性とユーザーエクスペリエンスを向上させるために重要です。try-catch
による例外処理やデータのバリデーション、デフォルト値の使用、容量オーバーの対応を組み合わせることで、型安全なlocalStorage
とsessionStorage
の操作をより堅牢に行うことができます。
次に、実際のプロジェクトでこれらのテクニックをどのように活用するかについて具体例を示します。
実際のプロジェクトでの適用例
TypeScriptでlocalStorage
やsessionStorage
を型安全に操作するための知識を得たら、次のステップはそれを実際のプロジェクトに適用することです。ここでは、型安全なストレージ操作を活用して、現場でどのように実装できるかについて、具体的なシナリオを基に解説します。
シナリオ1: ユーザー設定の保存
例えば、ウェブアプリケーションではユーザーがテーマ(ライトモードまたはダークモード)を選択する機能を提供することがよくあります。この設定はユーザーが再訪問しても保持されるように、localStorage
に保存されるのが一般的です。
実装例
type UserSettings = {
theme: "light" | "dark";
notificationsEnabled: boolean;
};
class SettingsManager {
static saveSettings(settings: UserSettings): void {
StorageService.setItem<UserSettings>("userSettings", settings);
}
static loadSettings(): UserSettings | null {
return StorageService.getItem<UserSettings>("userSettings");
}
static applySettings(): void {
const settings = this.loadSettings();
if (settings) {
document.body.className = settings.theme; // テーマを適用
console.log(`Notifications: ${settings.notificationsEnabled ? "Enabled" : "Disabled"}`);
}
}
}
// ユーザーが設定を保存する
const userSettings: UserSettings = { theme: "dark", notificationsEnabled: true };
SettingsManager.saveSettings(userSettings);
// アプリケーション起動時に設定を適用する
SettingsManager.applySettings();
このように、StorageService
を利用してUserSettings
型のデータを安全に保存・取得できます。設定データは型安全に保存され、取り出す際にも型が保証されるため、実行時に予期しないエラーが発生するリスクが大幅に減少します。
シナリオ2: ショッピングカートのデータ管理
ECサイトなどでは、ユーザーのショッピングカートの内容をブラウザに保持しておき、後で再度表示できるようにすることがよくあります。このデータもlocalStorage
に保存して、ユーザーがページを離れてもカートの内容を維持できます。
実装例
type CartItem = {
id: number;
name: string;
price: number;
quantity: number;
};
class CartManager {
static addItemToCart(item: CartItem): void {
const cart = StorageService.getItem<CartItem[]>("cart") || [];
cart.push(item);
StorageService.setItem<CartItem[]>("cart", cart);
}
static getCartItems(): CartItem[] {
return StorageService.getItem<CartItem[]>("cart") || [];
}
static calculateTotal(): number {
const cart = this.getCartItems();
return cart.reduce((total, item) => total + item.price * item.quantity, 0);
}
}
// カートにアイテムを追加
const newItem: CartItem = { id: 1, name: "Laptop", price: 1500, quantity: 1 };
CartManager.addItemToCart(newItem);
// カート内容の確認
const cartItems = CartManager.getCartItems();
console.log("Cart items:", cartItems);
// カートの合計金額を計算
const total = CartManager.calculateTotal();
console.log("Total:", total);
この例では、CartItem
型を定義し、型安全な方法でカートのアイテムを追加・取得しています。また、カート内のアイテムリストを取得し、合計金額を計算する機能も実装しています。このように、localStorage
を利用して、ショッピングカートのデータを安全に管理できるようになります。
シナリオ3: ユーザーのログインセッションの管理
ログイン情報をsessionStorage
に保存して、ブラウザを閉じるまでセッションを維持する場合もあります。sessionStorage
に保存されたデータはブラウザを閉じると削除されるため、一時的なセッション管理に適しています。
実装例
type UserSession = {
token: string;
userId: number;
expiresAt: string;
};
class SessionManager {
static saveSession(session: UserSession): void {
StorageService.setItem<UserSession>("userSession", session);
}
static loadSession(): UserSession | null {
return StorageService.getItem<UserSession>("userSession");
}
static clearSession(): void {
StorageService.removeItem("userSession");
}
static isSessionValid(): boolean {
const session = this.loadSession();
if (!session) return false;
const expiresAt = new Date(session.expiresAt);
return new Date() < expiresAt; // 有効期限をチェック
}
}
// セッションデータを保存
const session: UserSession = {
token: "abc123",
userId: 1,
expiresAt: new Date(Date.now() + 1000 * 60 * 60).toISOString(), // 1時間後
};
SessionManager.saveSession(session);
// セッションが有効か確認
if (SessionManager.isSessionValid()) {
console.log("Session is valid");
} else {
console.log("Session has expired or is invalid");
}
この実装では、sessionStorage
を使ってユーザーのセッション情報を管理しています。セッションが有効かどうかを判断するために、セッションの有効期限をチェックし、期限切れの場合はセッションを無効化する処理が行われます。
プロジェクト適用時のポイント
実際のプロジェクトで型安全なストレージ操作を適用する際、次の点に注意することが重要です。
- 型定義の徹底:データ構造が複雑になった場合でも、適切な型定義を行い、全てのデータ操作が型安全に行われるようにします。
- 一貫性のあるエラーハンドリング:エラーハンドリングを統一することで、データの破損や不整合を防ぎ、アプリケーションの信頼性を向上させます。
- 再利用可能なユーティリティの作成:ユーティリティ関数やクラスを活用することで、コードの再利用性を高め、保守性を向上させます。
これらのポイントを抑えれば、どんなプロジェクトでも安全かつ効率的にlocalStorage
やsessionStorage
を操作できるようになります。
次に、JSONデータの保存と取得について、さらに具体的な応用例を紹介します。
応用例: JSONデータの保存と取得
localStorage
やsessionStorage
に保存するデータは基本的に文字列として扱われますが、実際にはオブジェクトや配列などの構造化されたデータを保存・取得する必要があるケースがほとんどです。この際、データをJSON形式に変換して操作するのが一般的です。ここでは、JSONデータを型安全に保存・取得する方法とその応用例について解説します。
JSONデータの保存方法
JSON形式は、オブジェクトや配列などの構造化されたデータをテキスト形式で保存するのに適しており、localStorage
やsessionStorage
ではこの形式が広く使われています。TypeScriptを使用することで、保存前にデータの型をしっかりと定義しておくことができるため、データが不正な形式で保存されるリスクを最小限に抑えることが可能です。
保存の基本例
type Preferences = {
language: string;
theme: "light" | "dark";
};
// ユーザーの設定を保存する関数
function savePreferences(preferences: Preferences): void {
localStorage.setItem("preferences", JSON.stringify(preferences));
}
// 使用例
const userPreferences: Preferences = { language: "en", theme: "dark" };
savePreferences(userPreferences);
この例では、Preferences
という型を定義し、ユーザーの言語設定やテーマ設定を保存しています。JSON.stringify
によってオブジェクトを文字列に変換し、localStorage
に安全に保存できます。
JSONデータの取得方法
保存されたデータは、取得する際にJSON形式の文字列を元のオブジェクトに戻す必要があります。この際、JSON.parse
を使って文字列をパースする必要がありますが、型安全に扱うためにパース後のデータを適切な型にキャストすることが重要です。
取得の基本例
function loadPreferences(): Preferences | null {
const item = localStorage.getItem("preferences");
if (!item) return null;
try {
return JSON.parse(item) as Preferences;
} catch (error) {
console.error("Error parsing preferences:", error);
return null;
}
}
// 使用例
const storedPreferences = loadPreferences();
if (storedPreferences) {
console.log(`Language: ${storedPreferences.language}`);
console.log(`Theme: ${storedPreferences.theme}`);
}
この例では、localStorage
から取得したデータをJSON.parse
でオブジェクトに変換しています。また、パース中にエラーが発生した場合のためにtry-catch
を使ってエラーハンドリングも行っています。
配列やネストしたオブジェクトの扱い
実際のプロジェクトでは、配列やネストされたオブジェクトを扱うことがよくあります。これらのデータもJSON形式で保存・取得でき、TypeScriptの型を使用して安全に操作することができます。
配列データの保存と取得の例
type Todo = {
id: number;
task: string;
completed: boolean;
};
// Todoリストを保存する関数
function saveTodos(todos: Todo[]): void {
localStorage.setItem("todos", JSON.stringify(todos));
}
// Todoリストを取得する関数
function loadTodos(): Todo[] {
const item = localStorage.getItem("todos");
if (!item) return [];
try {
return JSON.parse(item) as Todo[];
} catch (error) {
console.error("Error parsing todos:", error);
return [];
}
}
// 使用例
const todos: Todo[] = [
{ id: 1, task: "Buy groceries", completed: false },
{ id: 2, task: "Read a book", completed: true }
];
saveTodos(todos);
const storedTodos = loadTodos();
console.log(storedTodos);
この例では、Todo
型を定義し、Todoリストを配列として保存しています。保存時にJSON.stringify
を使い、取得時にJSON.parse
を使うことで、配列のデータを安全に操作しています。
ネストされたオブジェクトの保存と取得の例
ネストされたオブジェクトも同様にJSONで扱うことができます。ここでは、ユーザー情報とその設定がネストされた構造を持つ例を示します。
ネストデータの保存と取得の例
type UserProfile = {
id: number;
name: string;
settings: {
theme: "light" | "dark";
language: string;
};
};
// ユーザープロファイルを保存する関数
function saveUserProfile(profile: UserProfile): void {
localStorage.setItem("userProfile", JSON.stringify(profile));
}
// ユーザープロファイルを取得する関数
function loadUserProfile(): UserProfile | null {
const item = localStorage.getItem("userProfile");
if (!item) return null;
try {
return JSON.parse(item) as UserProfile;
} catch (error) {
console.error("Error parsing user profile:", error);
return null;
}
}
// 使用例
const userProfile: UserProfile = {
id: 1,
name: "Alice",
settings: {
theme: "dark",
language: "en"
}
};
saveUserProfile(userProfile);
const storedUserProfile = loadUserProfile();
if (storedUserProfile) {
console.log(`User: ${storedUserProfile.name}`);
console.log(`Theme: ${storedUserProfile.settings.theme}`);
}
この例では、ユーザープロファイルにネストされた設定を含んだオブジェクトを保存しています。保存時と取得時の型安全性を保証することで、ネストされたデータの操作も安全に行えます。
まとめ
JSONデータをlocalStorage
やsessionStorage
に保存・取得する際には、TypeScriptの型を活用して、データの一貫性と安全性を確保することができます。単純なデータ型だけでなく、配列やネストされたオブジェクトも型定義を使って安全に操作できます。また、エラーハンドリングやデータのバリデーションを組み込むことで、信頼性の高いコードを実現できます。
次に、型安全なコードにおけるテストとデバッグのポイントについて解説します。
テストとデバッグのポイント
localStorage
やsessionStorage
を型安全に操作するコードを開発した後、そのコードが正しく動作するかどうかを確認するためのテストとデバッグは非常に重要です。ここでは、型安全なストレージ操作を検証するための効果的なテスト方法と、実際のプロジェクトでのデバッグ時に気をつけるべきポイントについて説明します。
テスト環境の構築
TypeScriptを用いたWebアプリケーションの開発では、単体テストを導入してコードの品質を担保することが一般的です。ストレージ操作も例外ではなく、JestやMochaなどのテストフレームワークを使ってユニットテストを行います。特にlocalStorage
やsessionStorage
に対してはモック(擬似的な機能)を使用することで、ブラウザの実行環境に依存せずテストが可能です。
テスト用の`localStorage`モック
通常のブラウザ環境がなくてもlocalStorage
を操作できるように、テスト用のモックを用意します。Jestの場合、グローバルのlocalStorage
を簡単にモックできます。
beforeEach(() => {
const localStorageMock = (() => {
let store: { [key: string]: string } = {};
return {
getItem(key: string) {
return store[key] || null;
},
setItem(key: string, value: string) {
store[key] = value;
},
removeItem(key: string) {
delete store[key];
},
clear() {
store = {};
}
};
})();
Object.defineProperty(window, 'localStorage', { value: localStorageMock });
});
test('should save and load user preferences', () => {
const preferences = { language: 'en', theme: 'dark' };
localStorage.setItem('preferences', JSON.stringify(preferences));
const storedPreferences = JSON.parse(localStorage.getItem('preferences')!);
expect(storedPreferences).toEqual(preferences);
});
このコードでは、テスト用にlocalStorage
の動作をモックし、setItem
とgetItem
が正しく機能するかどうかを確認しています。実際のブラウザに依存しないテスト環境を構築することができます。
型安全なコードのテスト戦略
型安全なコードでは、データの整合性と型の保証をチェックすることが重要です。次のようなテストを導入することで、型安全なコードをより信頼性の高いものにすることができます。
1. 型の一致を確認するテスト
保存されたデータと取得されたデータが、型定義通りであることを確認するテストを行います。例えば、ユーザーの設定が正しい型で保存され、正しく取り出されることを検証するテストです。
test('should store and retrieve preferences with correct types', () => {
const preferences: Preferences = { language: 'en', theme: 'dark' };
StorageService.setItem<Preferences>('preferences', preferences);
const storedPreferences = StorageService.getItem<Preferences>('preferences');
expect(storedPreferences?.language).toBe('en');
expect(storedPreferences?.theme).toBe('dark');
});
このテストでは、保存されたデータが取り出される際、正しい型であることをチェックしています。
2. エラーハンドリングのテスト
JSON.parse
によるデータ取得時にエラーが発生した場合、適切にハンドリングされるかをテストします。これにより、誤った形式のデータがストレージに保存された場合でも、アプリケーションがクラッシュしないことを確認します。
test('should handle invalid JSON data gracefully', () => {
localStorage.setItem('invalidData', 'not a JSON string');
const storedData = StorageService.getItem<Preferences>('invalidData');
expect(storedData).toBeNull(); // エラーハンドリングによりnullが返る
});
このように、誤ったデータが保存された場合でも、アプリケーションがクラッシュせずに適切なエラーハンドリングが行われることを確認できます。
デバッグのポイント
開発中のデバッグも重要な要素です。特にストレージの操作は、ブラウザの開発者ツールを活用してデバッグするのが効果的です。
1. ブラウザの開発者ツールを活用する
ほとんどのモダンブラウザは、localStorage
やsessionStorage
の内容を確認・操作できる開発者ツールを提供しています。これを使えば、保存されたデータが正しく保存されているかどうかをすぐに確認することができます。
- ブラウザの開発者ツールを開く(F12キーや右クリックで「検証」を選択)。
- 「Application」タブを選択し、
localStorage
やsessionStorage
を確認。 - ストレージの内容を直接編集したり、削除したりできるため、動作の確認やデバッグが容易になります。
2. ログ出力を利用する
コード内で適切な場所にconsole.log
を挿入することで、ストレージの状態やデータの処理過程を可視化できます。特に、localStorage
やsessionStorage
のデータが正しく保存・取得されているかを確認する際には、ログが有効です。
function loadUserData(): User | null {
const item = localStorage.getItem('user');
console.log('Retrieved item from localStorage:', item); // デバッグ用ログ
if (!item) return null;
try {
return JSON.parse(item) as User;
} catch (error) {
console.error('Error parsing user data:', error);
return null;
}
}
ログを出力することで、ストレージにどのようなデータが保存されているか、パースが成功しているかをリアルタイムで確認できます。
自動化されたテストの実行
最後に、テストを自動化して継続的に実行することを推奨します。テストをCI/CDパイプラインに組み込むことで、新しいコードの変更が既存の型安全な操作に悪影響を与えていないかを自動的にチェックできます。JestなどのテストフレームワークをCIツールと統合することで、開発プロセス全体の品質を向上させることができます。
まとめ
テストとデバッグは、型安全なコードが確実に機能することを確認するための重要なステップです。localStorage
やsessionStorage
の操作に関するテストでは、型の一致やエラーハンドリングに焦点を当てることが必要です。また、開発者ツールやログ出力を活用することで、ストレージ操作のデバッグがスムーズになります。これらのテストとデバッグ手法を導入することで、型安全なコードの信頼性が一層高まります。
次に、型安全なlocalStorage
操作を実際に実装してみるための演習問題を紹介します。
演習問題: 型安全な`localStorage`操作の実装
ここまで学んだ内容を活用し、実際に型安全なlocalStorage
操作を実装してみましょう。以下に3つの演習問題を用意しました。これらの問題を通じて、型安全なデータの保存・取得、エラーハンドリング、テストの実装方法を深く理解できるようになります。
問題1: ユーザー情報の保存と取得
まず、ユーザー情報をlocalStorage
に保存し、後から正しく取得できるような型安全なコードを実装してください。ユーザー情報は次のような型で保存されます。
type UserProfile = {
id: number;
username: string;
email: string;
};
実装内容:
UserProfile
型のデータをlocalStorage
に保存する関数saveUserProfile
を実装してください。UserProfile
型のデータをlocalStorage
から取得する関数getUserProfile
を実装してください。- データ取得時にエラーハンドリングを行い、取得に失敗した場合は
null
を返すようにしてください。
目標:
const user: UserProfile = {
id: 1,
username: "john_doe",
email: "john@example.com",
};
saveUserProfile(user); // データを保存
const storedUser = getUserProfile(); // データを取得
console.log(storedUser);
問題2: 配列データの保存と取得
次に、ユーザーのTodoリストをlocalStorage
に保存し、正しく取得できるようにしてください。Todoリストは次のような型で定義されます。
type Todo = {
id: number;
task: string;
completed: boolean;
};
実装内容:
Todo[]
型の配列をlocalStorage
に保存する関数saveTodos
を実装してください。Todo[]
型の配列をlocalStorage
から取得する関数getTodos
を実装してください。データがない場合は空の配列を返すようにします。
目標:
const todos: Todo[] = [
{ id: 1, task: "Learn TypeScript", completed: false },
{ id: 2, task: "Build a project", completed: false },
];
saveTodos(todos); // データを保存
const storedTodos = getTodos(); // データを取得
console.log(storedTodos);
問題3: エラーハンドリング付きの設定データ管理
最後に、ユーザー設定をlocalStorage
で管理するプログラムを作成します。次の型で設定を保存してください。
type Settings = {
theme: "light" | "dark";
notificationsEnabled: boolean;
};
実装内容:
Settings
型のデータを保存する関数saveSettings
を実装してください。Settings
型のデータを取得する関数getSettings
を実装してください。データ取得時に不正なJSONが含まれている場合に、エラーハンドリングを実装し、null
を返すようにします。
目標:
const settings: Settings = {
theme: "dark",
notificationsEnabled: true,
};
saveSettings(settings); // データを保存
const storedSettings = getSettings(); // データを取得
console.log(storedSettings);
補足: テストの実装
上記の演習問題に対して、Jestなどを使ってテストケースを実装してください。特に、エラーハンドリングや型の検証が正しく行われているかどうかを確認するテストを書くことが目標です。
テストの例:
test('should save and load user profile', () => {
const user: UserProfile = { id: 1, username: "john_doe", email: "john@example.com" };
saveUserProfile(user);
const storedUser = getUserProfile();
expect(storedUser).toEqual(user);
});
まとめ
この演習問題では、型安全なlocalStorage
操作を実際にコードで実装することで、型システムを活用した安全なデータ管理方法を習得します。型定義、エラーハンドリング、配列やオブジェクトの扱いなど、TypeScriptの利点を最大限に活用して効率的にストレージを操作できるようになります。
まとめ
本記事では、TypeScriptを活用してlocalStorage
やsessionStorage
を型安全に操作する方法について、基礎から応用までを解説しました。型安全な操作の重要性、Genericsを使った汎用的な解決策、エラーハンドリングのベストプラクティス、実際のプロジェクトでの適用例、さらにJSONデータの扱いやテスト・デバッグのポイントまでを網羅しました。これらの知識を活用することで、ストレージ操作に伴うバグを減らし、信頼性の高いWebアプリケーションを開発できます。演習問題を通して、ぜひ実際のプロジェクトに適用し、より実践的なスキルを磨いてください。
コメント