IndexedDB でアプリケーションの状態を保持するためのベスト プラクティス

IndexedDB と一般的な状態管理ライブラリの間でアプリケーションの状態を同期するためのベスト プラクティスを学びます。

ユーザーがウェブサイトまたはアプリを初めて読み込むとき、UI のレンダリングに使用される初期のアプリケーション状態の構築に多くの作業が伴うことがよくあります。たとえば、ページに表示する必要があるすべてのデータを取得するために、アプリがクライアントサイドでユーザーを認証し、その後に複数の API リクエストを行う必要がある場合があります。

アプリケーションの状態を IndexedDB に保存すると、再訪問の読み込み時間を短縮できます。その後アプリは、古い再検証戦略を採用して、バックグラウンドで API サービスと同期し、新しいデータで UI を遅延更新できます。

ただし、IndexedDB を使用する場合は、API を初めて使用するデベロッパーにはすぐにはわからない重要な点がいくつかあります。このドキュメントでは、よくある質問に回答し、IndexedDB でアプリケーションの状態を保持する際に考慮すべき最も重要な点について説明します。

アプリの予測可能性を維持する

IndexedDB の複雑さの多くは、デベロッパーが制御できない要素が非常に多いという事実に起因しています。このセクションでは、IndexedDB を使用する際に留意すべき多くの問題について説明します。

ストレージへの書き込みが失敗する可能性があります

IndexedDB への書き込み時のエラーはさまざまな原因で発生しますが、デベロッパーが制御できない場合もあります。たとえば、一部のブラウザでは、シークレット モードで IndexedDB への書き込みが許可されていません。また、ディスク容量が不足しているデバイスにユーザーがいる可能性があり、ブラウザは何も保存できないように制限します。

そのため、IndexedDB コードに適切なエラー処理を常に実装することが重要です。また、通常は、(保存するだけでなく)アプリケーションの状態をメモリに保持しておくこともおすすめします。そうすれば、プライベート ブラウジング モードで実行しているときや、ストレージ容量が不足しているときでも、UI が破損することはありません(ストレージを必要とする他のアプリ機能の一部が機能しない場合でも同様です)。

保存されたデータがユーザーによって変更または削除された可能性があります

不正アクセスを制限できるサーバーサイド データベースとは異なり、クライアントサイド データベースはブラウザ拡張機能とデベロッパー ツールにアクセスでき、ユーザーが消去できます。

ローカルに保存されたデータをユーザーが変更することは一般的ではありませんが、ユーザーが消去することはよくあります。アプリケーションがエラーを発生させることなく、これらのケースの両方を処理できるようにすることが重要です。

保存されたデータは最新でない可能性があります

前のセクションと同様に、ユーザーが自分でデータを変更していなくても、ストレージ内のデータが古いバージョンのコード(バグのあるバージョン)によって書き込まれた可能性もあります。

IndexedDB には、スキーマ バージョンと IDBOpenDBRequest.onupgradeneeded() メソッドを使用したアップグレードの組み込みサポートがありますが、以前のバージョン(バグのあるバージョンを含む)からユーザーが移行できるようにアップグレード コードを記述する必要があります。

単体テストは、考えられるすべてのアップグレード パスとケースを手動でテストすることが現実的でないことが多いため、ここで非常に役立ちます。

アプリのパフォーマンスを維持する

IndexedDB の重要な機能の一つは非同期 API ですが、それによって、使用時にパフォーマンスを気にする必要はないという思いが浮かんではいけません。不適切な使用によってメインスレッドがブロックされ、応答しなくなるケースは数多くあります。

原則として、IndexedDB の読み取りと書き込みは、アクセス対象のデータに必要なサイズを超えないようにしてください。

IndexedDB では、ネストされた大きなオブジェクトを 1 つのレコードとして格納できます(デベロッパーにとってはこれが非常に便利です)。ただし、このような手法は避けてください。これは、IndexedDB がオブジェクトを保存するときに、まずそのオブジェクトの構造化クローンを作成する必要があり、構造化クローン作成プロセスがメインスレッドで行われるためです。オブジェクトが大きいほど、ブロック時間は長くなります。

一般的な状態管理ライブラリ(Redux など)のほとんどは、状態ツリー全体を単一の JavaScript オブジェクトとして管理することで機能するため、アプリケーションの状態を IndexedDB に維持する方法を計画する際には、いくつかの課題が生じます。

この方法で状態を管理することには多くのメリットがありますが、状態ツリー全体を IndexedDB に 1 つのレコードとして保存するのは魅力的で便利ですが、変更のたびに(スロットリングまたはデバウンスされた場合でも)メインスレッドが不必要にブロックされることになり、書き込みエラーが発生する可能性が高くなり、場合によってはブラウザタブがクラッシュしたり、応答しなくなったりする可能性があります。

状態ツリー全体を 1 つのレコードに格納するのではなく、個々のレコードに分割し、実際に変更されたレコードのみを更新する必要があります。

ほとんどのベスト プラクティスと同様に、これは「すべてまたは何も」のルールではありません。状態オブジェクトを分割して最小の変更セットのみを書き込むことが現実的でない場合は、状態ツリー全体を常に書き込むよりも、データをサブツリーに分割してそれらのみを書き込むことをお勧めします。改善がほとんどないということは、改善をまったく行わないよりは良いことです。

最後に、作成するコードのパフォーマンスへの影響を常に測定する必要があります。IndexedDB への小さな書き込みは大きな書き込みよりもパフォーマンスが高いのは事実ですが、これは、アプリが行っている IndexedDB への書き込みが、メインスレッドをブロックしてユーザー エクスペリエンスを低下させる長いタスクに実際につながる場合にのみ重要です。測定を行うことで、何を最適化しているかを把握することが重要です。

まとめ

IndexedDB などのクライアント ストレージ メカニズムを使用すると、セッション間で状態を保持するだけでなく、再訪問時に初期状態を読み込む時間を短縮することで、アプリケーションのユーザー エクスペリエンスを改善できます。

IndexedDB を適切に使用するとユーザー エクスペリエンスは劇的に向上しますが、適切に使用しない場合やエラーケースの処理に失敗すると、アプリが壊れたり、ユーザーが不満を抱いたりする可能性があります。

クライアント ストレージには、制御できない多くの要因が関係するため、コードを十分にテストし、最初は発生しそうにないエラーであっても適切に処理することが重要です。