IndexedDB と一般的な状態管理ライブラリの間でアプリケーションの状態を同期するためのベスト プラクティスについて説明します。
ユーザーがウェブサイトやアプリケーションを初めて読み込むとき、UI のレンダリングに使用される初期アプリケーション状態の構築には、多くの場合、かなりの作業が伴います。たとえば、アプリがクライアントサイドでユーザーを認証し、ページに表示する必要があるすべてのデータを取得する前に、複数の API リクエストを行う必要がある場合があります。
アプリケーションの状態を IndexedDB に保存すると、リピート訪問時の読み込み時間を短縮できます。アプリはバックグラウンドで API サービスと同期し、stale-while-revalidate 戦略を使用して、新しいデータで UI を遅延更新できます。
ただし、IndexedDB を使用する際には、API を初めて使用するデベロッパーにはすぐにはわからない重要な考慮事項が数多くあります。このドキュメントでは、よくある質問に回答し、IndexedDB でアプリケーションの状態を保持する際に留意すべき重要なポイントについて説明します。
アプリの予測可能性を維持する
IndexedDB に関する複雑さの多くは、開発者が制御できない要素が多数あることに起因しています。このセクションでは、IndexedDB を使用する際に留意すべき多くの問題について説明します。
ストレージへの書き込みが失敗する可能性がある
IndexedDB への書き込み時にエラーが発生する理由はさまざまです。場合によっては、デベロッパーが制御できない理由もあります。たとえば、一部のブラウザでは、シークレット ブラウジング モードで IndexedDB への書き込みが許可されていません。また、ユーザーがディスク容量がほとんどないデバイスを使用している可能性もあり、ブラウザによって保存が制限されることもあります。
そのため、IndexedDB コードでは常に適切なエラー処理を実装することが非常に重要です。また、一般的には、アプリケーションの状態を(保存するだけでなく)メモリに保持することをおすすめします。そうすることで、シークレット ブラウジング モードで実行している場合や、ストレージ容量が不足している場合でも、UI が破損することはありません(ストレージを必要とする他のアプリ機能の一部が動作しない場合でも)。
保存されたデータがユーザーによって変更または削除されている可能性があります。
不正アクセスを制限できるサーバーサイド データベースとは異なり、クライアントサイド データベースはブラウザ拡張機能やデベロッパー ツールからアクセス可能で、ユーザーが消去できます。
ユーザーがローカルに保存されたデータを変更することはあまりありませんが、ユーザーがデータを消去することはよくあります。アプリケーションがエラーなしでこれらの両方のケースを処理できることが重要です。
保存されているデータが古い可能性があります
前のセクションと同様に、ユーザーがデータを変更していなくても、ストレージに保存されているデータが、以前のバージョンのコード(バグを含むバージョンなど)によって書き込まれた可能性もあります。
IndexedDB には、IDBOpenDBRequest.onupgradeneeded() メソッドを使用したスキーマ バージョンとアップグレードのサポートが組み込まれています。ただし、以前のバージョン(バグのあるバージョンを含む)から移行するユーザーを処理できるように、アップグレード コードを記述する必要があります。
考えられるすべてのアップグレード パスとケースを手動でテストすることは不可能な場合が多いため、単体テストが非常に役立ちます。
アプリのパフォーマンスを維持する
IndexedDB の主な機能の一つは非同期 API ですが、これを使用する際にパフォーマンスを気にする必要がないと考えるのは間違いです。不適切な使用によりメインスレッドがブロックされ、応答しなくなるケースがいくつかあります。
一般的なルールとして、IndexedDB への読み取りと書き込みは、アクセスされるデータに必要な量を超えないようにする必要があります。
IndexedDB では、大きなネストされたオブジェクトを単一のレコードとして保存できます(これはデベロッパーの観点からは非常に便利です)。ただし、この方法は避けてください。その理由は、IndexedDB がオブジェクトを保存するときに、まずそのオブジェクトの構造化クローンを作成する必要があり、構造化クローン作成プロセスがメインスレッドで行われるためです。オブジェクトが大きいほど、ブロック時間が長くなります。
これは、アプリケーションの状態を IndexedDB に永続化する方法を計画する際に、いくつかの課題をもたらします。一般的な状態管理ライブラリ(Redux など)のほとんどは、状態ツリー全体を単一の JavaScript オブジェクトとして管理することで機能するためです。
この方法で状態を管理することには多くのメリットがあり、状態ツリー全体を IndexedDB に単一のレコードとして保存することは魅力的で便利ですが、変更のたびにこれを行うと(スロットル / デバウンスされている場合でも)、メインスレッドの不要なブロックが発生し、書き込みエラーの可能性が高まります。場合によっては、ブラウザタブがクラッシュしたり、応答しなくなったりすることもあります。
状態ツリー全体を 1 つのレコードに保存するのではなく、個々のレコードに分割し、実際に変更されたレコードのみを更新する必要があります。
ほとんどのベスト プラクティスと同様に、これは「すべてか無か」のルールではありません。状態オブジェクトを分割して最小限の変更セットのみを書き込むことが不可能な場合は、データをサブツリーに分割して、それらのみを書き込む方が、常に状態ツリー全体を書き込むよりも望ましい方法です。小さな改善でも、まったく改善しないよりはましです。
最後に、作成したコードのパフォーマンスへの影響を常に測定する必要があります。IndexedDB への書き込みは、サイズが小さいほどパフォーマンスが向上するのは事実ですが、これが問題になるのは、アプリケーションが IndexedDB への書き込みを行うことで、メインスレッドをブロックしてユーザー エクスペリエンスを低下させる長いタスクが発生する場合のみです。何を最適化しているのかを把握するために、測定することが重要です。
まとめ
デベロッパーは、IndexedDB などのクライアント ストレージ メカニズムを使用して、セッション間で状態を保持するだけでなく、リピート訪問時に初期状態を読み込む時間を短縮することで、アプリケーションのユーザー エクスペリエンスを向上させることができます。
IndexedDB を適切に使用すると、ユーザー エクスペリエンスを大幅に改善できますが、誤って使用したり、エラーケースを処理できなかったりすると、アプリが破損し、ユーザーの不満につながる可能性があります。
クライアント ストレージには、制御できない多くの要因が関係しているため、コードを十分にテストし、エラーを適切に処理することが重要です。