メインコンテンツにジャンプ

アプリケーション状態管理: アーキテクチャとSyncガイド

アプリケーション状態管理をマスターし、Capacitor と Electron に最適化する。アーキテクチャパターン、オフラインファーストの同期、パフォーマンスチューニング、移行戦略を探索する。

アプリケーション状態管理: アーキテクチャとSyncガイド

最も人気のあるアドバイスは、実際には最も無用なものでもある: 1つのグローバルストアを選んで、すべてをそこに格納することだ。 そのアプローチでは、__CAPGO_KEEP_0__ のレスポンス、モーダル表示、未保存のフォーム入力、認証、ナビゲーションフィルタなど、すべてのライフサイクルを同じものとして扱う。 しかし、それらは違う。 アプリケーション状態管理 is also the least useful: pick one global store and put everything in it. That approach treats API responses, modal visibility, unsaved form input, authentication, and navigation filters as if they had the same lifecycle. They don’t. A Capacitor or Electron application runs across a web layer and native or desktop runtime, so state must be separated by 状態管理は、モバイルランタイムが不安定であるため、基礎となるものになった。 アプリケーションは、オリエンテーション変更や低メモリ条件によって破棄され、再作成される可能性がある。 Android は、インスタンス状態の復元とライフサイクルコールバックを提供している。.

その理由については、UC Riverside の研究で詳しく説明されている。 onSaveInstanceState() 信頼性の高いモバイルアプリケーション状態管理に関する研究 同研究では、966 のアプリケーションと 4,808 のアクティビティを調査した。 結果は、アプリケーション 452 個、つまり 46.8% が、少なくとも 1 つのアクティビティに非空の状態を含んでいたことがわかった。 アプリケーション 452 個、つまり 46.8% が、少なくとも 1 つのアクティビティに非空の状態を含んでいたことがわかった。アプリケーション 452 個、つまり 46.8% が、少なくとも 1 つのアクティビティに非空の状態を含んでいたことがわかった。 アプリケーション 452 個、つまり 46.8% が、少なくとも 1 つのアクティビティに非空の状態を含んでいたことがわかった。、 1,896のアクティビティ 全体的に非空の状態を持っていた。状態は、UIが機能する後で追加できるオプションの抽象化ではありません。実行時契約の一部です。

目次

グローバルストアモデルを再考する

グローバルストアパラダイムを再考するという概念図

Redux と Context と MobX は間違った出発点です。ストアは更新を調整できますが、値がサーバー、現在の画面、フォームワークフロー、またはアドレスバーに属しているかどうかを決定することはできません。すべての値を 1 つのグローバル コンテナに格納すると、重複した API キャッシュ、古い URL パラメータ、および無関係な画面の再レンダリングが発生します。

持続可能な設計では、各値に所有者と復元ポリシーを割り当てます。

  • サーバー ステート サーバー ステートはデータ フェッチングとキャッシュ層に属します。 API 応答、ロード ステータス、エラー、古さ、無効化、リトライはすべてリモート リソースを表します。サーバーは最終的な権威を持ち、クライアントは 2 番目の永久的な真実の源を維持する必要はありません。
  • クライアントまたは UI ステート UI ステートは、所有するコンポーネントまたは機能の近くに残ります。モーダル表示、選択されたタブ、展開された行、テーマの好み、暫定的なインタラクション フラグは、通常、全アプリケーションでの永続化が必要ありません。
  • フォーム ステート フォーム ステートは別のワークフローに従います。入力の草稿、検証メッセージ、汚れのステータス、送信の進行度は、フォーム内でナビゲーションを乗り越える必要があるかもしれませんが、自動的に共有されたビジネス ステートにはなりません。
  • URL ステート URL ステートはルーターに属します。検索条件、フィルタ、ソートオプション、選択されたレコード、ページネーションは、別のストアにコピーすることなくブックマーク、共有、復元、検査できます。

1 つのストアがアーキテクチャ的負荷を引き起こす理由

2024 アプリケーション ステート管理の詳細なレビュー Web アプリケーションとモバイル アプリケーションを横断するステート管理を調査します。カバー範囲は、散在した UI 変数から明示的なライフサイクル認識アーキテクチャへの実用的なシフトを反映しています。Android の進化、インスタンス ステート バンドルから ViewModel と SavedStateHandle まで、同じ方向に進んでいますが、それはすべての値が共有されたコンテナ内に含まれる必要があることを意味するわけではありません。

グローバル ストアは、定義された役割を持ちます。セッション ID、権限、アプリ レベル プリファレンス、接続性 ポリシー、慎重に境界付けられたクロス フィーチャー ワークフローには、共有所有権が必要です。問題は、ストアが値の適切な所有者に適した値の捨て場になることです。

実用的なルール: 値がサーバー、URL、または現在のコンポーネントから再構築できる場合、グローバル ステートから除外してください。特定のワークフローが必要な場合を除きます。

この境界は、独立した所有権のモジュールをサポートします。製品を展開可能なエリアに分割するチームは、micro-frontend アーキテクチャで使用される所有権の原則を適用できます。代わりに、依存グラフをアプリケーション全体に構築するのではなく。 Electron コードベースや __CAPGO_KEEP_0__ のようなケースでは、ハイブリッド モデルが効果的です: リモート データはキャッシュと同期ルールを使用し、UI 値はローカルに残り、耐久性のあるワークフロー ステートは明示的なパERSISTENCEを使用します。この分離は、競合する書き手の数を制限し、再読み込み、停止したプロセス、またはオフライン期間の回復をより論理的に推論できるようにします。, rather than building one dependency graph across the application. In a Capacitor or Electron codebase, a hybrid model works better: remote data uses cache and synchronization rules, UI values remain local, and durable workflow state gets explicit persistence. That separation limits competing writers and makes recovery after reloads, suspended processes, or offline periods easier to reason about.

アプリケーション ステート管理の詳細なレビュー

状態が所有者を持つと、実装の選択肢はかなり狭くなります。ほとんどのクライアントサイドの状態は、以下の3つのパターンのいずれかに合致します: ローカライズされたコンポーネントの状態, 小さな共有ストア, または 隔離されたモジュール間のイベントドリブンコミュニケーション どれも普遍的に優れているわけではありません。チームがパターンを選択する前に、更新頻度、所有権、復旧要件を特定しないと、誤った選択が現れます。

パターン 複雑さ メモリ フットプリント ベスト ユース ケース
ローカライズされたコンポーネントの状態 低 低 画面ごとのスイッチ、草稿、非表示のパネル、選択中のテキスト
軽量の集中管理ストア 中 中 共有セッションの設定、テーマ、現在のワークスペース、機能間のUIの調整
イベントバスアーキテクチャ 中から高 変数 モジュール間の疎結合、プラグインの通知、ネイティブブリッジのイベント、機能間の隔離

ローカル状態はデフォルトでなければなりません

コンポーネントローカルの値は、依存関係のパスが短い。モーダルが開いたり、行が拡大したり、フォームフィールドが変更されたりしたとき、所有する機能は通知することなく更新できる。全体のアプリケーションに通知する必要がないため、意図しない結合を減らし、単体テストを直接化できる。モバイル画面が一時停止したり、デスクトップウィンドウが長時間開いたりしたときに保持されるメモリを制限することもできる。

ローカルステートは、複数の遠隔の機能が同じ値を必要とする場合や、ワークフローがルート境界を越える場合に不快になる。そういった場合、特定の機能のストアにステートを上げることが、全体のアプリケーションに単一のオブジェクトを置くよりも良いであろう。小さなストアとしてZustandやPiniaを使用すると、特定のセレクターと明示的なアクションを公開できるが、すべてのコンポーネントがすべての変更にサブスクライブする必要がない。

このトレードオフは、規律である。軽量なストアを作成するのは容易なので、チームは多くの重複するストアと不明確な所有権を持つことになる。所有者を名付け、変異メソッドを定義し、任意のコンポーネントが書き換えることができる可変オブジェクトを公開しないようにする。

イベントは便利だが、データベースではありません。

イベントバスは、通知として「ネイティブシェアが完了した」、「ウィンドウがアクティブになった」、「バックグラウンドタスクが新しいデータを受け取った」などのメッセージを送信するのに適している。プラグインやモジュール間の通信を可能にするが、ビジネスステートの唯一の記録としては機能しない。サブスクライバーが一時停止した、アンロードされた、または遅れて登録された場合、メッセージを失う可能性がある。

イベントを使用して、特定のイベントが発生したことを受信モジュールに通知し、受信モジュールはその権威あるストアまたはデータレイヤーを問い合わせる。イベント名は狭く、ネイティブとウェブのcodeが別々に進化する場合、ペイロードはバージョン管理する。

より広いアーキテクチャ的背景については モバイルアプリ開発の知見はBridge Globalから are useful when weighing shared code against platform-specific behavior. The same boundary applies to app state: share domain rules where they are stable, but isolate lifecycle adapters and native integration points. A practical 持続可能なデータとオフラインファーストの同期 フォルダ構造と依存関係グラフで、境界を明確にする必要があります。

持続可能な書き込みパスを作成する

信頼できるフローは、直ちにユーザー体験とリモートの承認を分離します:

ローカルに書き込む

ユーザー操作をローカルなデータベースまたは持続可能なドキュメントストアに適用し、インターフェイスはネットワークに待たずに反応するようにします。

  1. ローカルで書き始めてください。 モバイルアプリ開発の洞察
  2. ミューテーションをキューイングします。 エンティティID、オペレーションタイプ、ペイロード、作成コンテキスト、リトライステータスを含むオペレーションを保存します。メモリ上のみで保持されるキューはプロセス終了とともに消えます。
  3. 正直なステータスをレンダリングします。 ローカルに保存、同期待ち、同期済み、失敗したと区別します。ユーザーは、デバイス上で変更が持続するか、リモートで確認されるかを知りたいのです。
  4. 条件が許す限り同期します。 リスナーの再接続、 foreground イベント、スケジュールされたバックグラウンドワークがリトライをトリガーする可能性があるため、sync ワーカーは idempotent でなければなりません。中断された要求が再送信される可能性があるためです。
  5. 紛争を意図的に解決します。 サーバーからの応答は、ローカルオペレーションが受け入れられた、却下された、マージされた、またはユーザーによるレビューが必要なものであるかを決定する必要があります。

SQLiteは、ネイティブバックアップされたアプリケーションにおける関連レコードとトランザクションキューの実用的な選択肢です。IndexedDBは、Electronレンダラーcodeを含むブラウザ指向のストレージに適しています。チームはスキーマアップグレードとトランザクション境界を慎重に管理する必要があります。シリアライズを明示的に行います。ドメインデータとリカバリーメタデータを保存し、コンポーネントインスタンス、クロージャ、ネイティブオブジェクトへの参照を保存しないでください。

ソフトウェアアプリケーションのパERSISTENCEとオフラインファースト同期のプロセスを説明する5ステップの図です。

紛争ポリシーは製品の決定です

最後の書き込みが優先されることは簡単ですが、有効な編集を捨てる可能性があります。フィールド単位のマージは、独立したフィールドが安全に組み合わせることができる場合に機能します。ドメイン固有のルールは、自動マージが意味を変える可能性があるインベントリ、承認、財務記録、臨床ワークフローなどの場合に安全です。あるいは、ユーザーに選択を求めるために同期をブロックするべきであるべきはあるべきです。

Sync層も分離する必要があります。 読み直し再同期 から mutation replayサーバー データの再取得は、キュー内で成功したミューテーションを証明するものではなく、ミューテーションを再生することは、サーバー上の現在の表現と一致する結果レコードが残っていることを保証するものでもありません。サーバー バージョンを記録するか、同等の検証者を使用し、構造化されたコンフリクト レスポンスを返し、失敗の説明に十分なキュー履歴を保持する必要があります。

このデザインのユーザーフェイス部分では オフライン画面を作成する オフラインモードは、ユーザーにわかりやすいアプリケーション状態として表示されるべきであり、コンソールに隠された例外ではありません。

オフライン動作と同期に関する実装作業を補完する動画は以下です。

パフォーマンスチューニングとテスト戦略

ステートのパフォーマンス問題は、単一の遅いレデューサーから始まることはほとんどありません。広範なサブスクリプションが原因で、無関係なコンポーネントがレンダリングされる、派生値が不必要に再計算される、大きなオブジェクトグラフが参照される、またはUIスレッド上で同期作業が実行されるなど、複数の要因が原因となります。更新の伝播を測定するのではなく、どのライブラリが最速かを推測するのではなく、パフォーマンスを測定してください。

A 2026のベンチマークで、100の接続コンポーネントと10,000の反復を使用したダッシュボード 100の接続コンポーネントと10,000の反復 MobXは0.3msでシンプルな変更、0.4msでネストされた変更、0.6msで派生された変更を測定しました 0.3msのシンプルな変更、0.4msのネストされた変更、0.6msの派生された変更Redux Toolkitは0.8ms、1.2ms、1.5msを測定しました 0.8ms、1.2ms、1.5ms Zustandは2.8MB、Redux Toolkitは4.2MBを記録しました 2.8MB、4.2MB これらはベンチマーク固有の観察であり、普遍的な生産保証ではありませんが、サブスクリプションの粒度と更新戦略の重要性を示しています。 サブスクリプションの粒度と更新戦略の重要性を示しています 比較的Reactの状態管理ベンチマークを参照してください

比較的Reactの状態管理ベンチマークを参照してください

最初はセレクタから始めましょう。コンポーネントは、セッションオブジェクト全体やAPIのレスポンスではなく、最小限の意味のあるスライスにサブスクライブするべきです。計算が費やすコストが高くなる場合、派生データをメモ化しておきましょうが、すべてのプリミティブを反射的にメモ化する必要はありません。メモ化も、保持と比較の作業を追加するため、プロファイルする前にとあとで行ってみましょう。

長いリストを仮想化し、更新が個々のエンティティにターゲットされているレコードを正規化し、根オブジェクトの大きい部分を小さなフィールドの変更で置き換えるのを避けましょう。Electronでは、長時間のセッション中、レンダラーメモリを監視する必要があります。ウィンドウは、モバイル画面が死んだ後も生き続ける可能性があるからです。Capacitorでは、入力のバースト中に同期的な保存を避けましょう。ドラフトを遅延させたり、意味のあるチェックポイントを保存したりしながら、製品が保持するデータを失うことなくクラッシュを許容するようにしてください。

平均プログラム実行時間が、Springer関連の研究で平均で約 値だけではなく、トランジションをテストする必要があります。状態テストが成功したリクエスト後にチェックする場合、危険なパスを無視することになります。次のシーケンスをテストする必要があります。 水分化:保存されたデータのロード、無効なレコードの拒否、欠落しているフィールドにデフォルト値を埋め込む

水分化

17%のプログラム実行時間の短縮 isLoading Webアプリケーション状態管理のパフォーマンス

  • Hydration: Webアプリケーション状態管理のパフォーマンス
  • 中断: リクエストがキャンセルされたり、Mutation中のアプリがバックグラウンドに遷移したりします。
  • 再生: キュー内のオペレーションは安全にリトライされ、サーバー効果の重複は防がれます。
  • 対立: サーバーは古いバージョンを拒否し、UIは回復可能な解決策を提示します。
  • 隔離: ローカルUIの更新は、無関係の機能をレンダリングまたは変化させることはありません。

ネットワーク境界でモックサーバー状態アダプターを使用し、完全なワークフローに対する統合テストを使用します。開発とCIで、予期せぬサブスクライバーの数、無限のキュー、特定の機能がアンマウントされた後で発生する状態の遷移を検出するためのインストルメンテーションを追加してください。最も価値のあるテストフィクスチャは、実際のライフサイクルシーケンスではなく、別の孤立したレデューサーのアサーションではありません。使用 アプリパフォーマンス最適化ガイダンス リリースチェックにそれらの測定値を繰り返し実行する際に使用します。

CapacitorとElectronのためのプラットフォーム固有のガイダンス

A web application can assume that its JavaScript process remains available longer than a mobile app can. Capacitor と Electron は、その仮定を異なる方法で排除します。 Capacitor は、iOS または Android のライフサイクルを管理するために、Web層を内部化し、Electron はデスクトッププロセスモデルとウィンドウを接続し、独立して表示および非表示にすることができます。

ウェブアプリケーションとネイティブアプリケーションの状態管理を示すノートパソコン、スマートフォン、ノートブック。

Capacitor はライフサイクル認識の水準を必要とします。

バックグラウンド化をプロセスが再開することの証明とみなすのではなく、チェックポイントの機会として扱うことができます。アプリケーション状態の変更時には、重要な保留中の変化をフラッシュし、現在の同期カーソルを記録し、有効でないまま残すべきリソースをリリースしてください。フォアグラウンド時には、失われたものを再水準化し、認証を検証し、古いサーバー データを更新し、ローカル ストアが準備されている場合にのみサブスクリプションを再起動してください。

JavaScript ストアは、ネイティブ プラグインの内部を直接所有してはなりません。カメラ セッション、バイオメトリクス プロンプト、プッシュ レジストレーション、ファイル システム ハンドル、バックグラウンド タスクなど、各プラットフォームにはライフサイクル ルールがあります。ネイティブ コールバックをドメイン イベントまたはコマンドに翻訳するアダプターで包み込んでください。ストアは、ネイティブ オブジェクトが一時停止後に無効になることを保証しながら、無効、要求中、有効、失敗、または完了した状態を表すことができます。

A webview boundary also makes serialization important. Pass plain data across the Capacitor bridge, validate plugin responses, and version messages when a live update may leave different web bundles interacting with installed native code. Capacitorとnative codeを繋ぐ __CAPGO_KEEP_0__とnative __CAPGO_KEEP_1__を繋ぐ

Electronにはプロセス所有権が必要

Electronのメインプロセスは、特権操作と持続可能な調整を所有し、レンダラーはビュー固有の状態を所有する必要があります。安全な設定を読み取ったりファイルを書き込んだりウィンドウを調整したりするアクションにタイプ付きIPCコマンドを使用してください。レンダラーに広範なファイルシステムアクセスを公開しないでください。また、1つのウィンドウに送信されたイベントを、持続可能なアプリケーションレコードとして扱いません。

マルチウィンドウアプリケーションには、明示的な同期モデルが必要です。メインプロセスは、権威ある更新を分配し、各レンダラーはローカルプレゼンテーション状態を維持します。2つのウィンドウが同じレコードを編集した場合、アプリケーションにはバージョンチェックまたは紛争ポリシーが必要です。ただし、単にブロードキャストイベントを送信するだけではありません。閉じたウィンドウは、再度開いたときに状態を復元できるようにする必要があります。したがって、メインプロセスまたは持続可能なレイヤーは、復元可能なデータの元になります。

CapacitorとElectronは、ドメインモデル、APIクライアント、キュー形式、そしてレデューサーを共有できます。ライフサイクルcodeを共有する必要はありません。最も強力なクロスプラットフォームアーキテクチャには、共通の状態用語とプラットフォーム固有の持続性、ブリッジ、そして復元アダプターが必要です。

モダンハイブリッドアーキテクチャへの移行

古いグローバルストアを書き直す必要はほとんどありません。インベントリと安全な抽出のシーケンスが必要です。移行は、古いストアが触れられていない画面に対しては利用可能な状態で、各機能が1つの状態クラスを1度ずつ移動できるようにすることが最も効果的です。

モダンハイブリッドアプリケーション状態アーキテクチャへの移行のプロセスを示す4段階の図。

所有権ではなく技術から始めましょう

既存のストアの状態カタログを作成します。各フィールドについて、元のソース、消費者、変化パス、永続化要件、復元動作を記録します。サーバーから派生した、ルートから派生した、フォーム所有権、機能ローカル、共有されたもののいずれかをマークします。この演習では、グローバルストアが複数の無関係なシステムが1つのAPIに隠されていることをよくわかります。

サーバー状態を最初に移動します。手動でミラーリングされたAPIフィールドを置き換え、リクエストステータス、無効化、リトライ、再検証を所有する専用のフェッチングとキャッシュレイヤーで置き換えます。セレクターを一時的に互換性があるようにしておきます。既存の画面が一度にすべての呼び出しサイトを変更することなく移行できるようにします。新しいソースが統合テストを通過した後、重複したサーバーカッピーを削除します。

次に、URL状態をルーターに戻します。検索フィルタと選択されたリソースは、ルートパラメータまたはクエリ状態を通じて再読み込みと共有をサポートする必要があります。URL値をストアにコピーし、ストア値をURLにコピーする同步効果を削除します。そうすると、レース条件が生じてブラウザの履歴を信頼できなくなるからです。

機能の状態を段階的に抽出する

モーダル状態、ウィザードの進行度、ローカル選択を機能の境界に近い位置に移動する。複数のコンポーネントが値を必要とする場合は、狭いインターフェイスを持つ機能ストアを使用する。残りのグローバルストアは、セッションポリシー、テーマ、権限、または明示的に共有されたワークフローなどのクロスカット関心事に予約する。

新しいソースから値を読み取ることができる互換性レイヤーを使用して移行を実行する。古いセレクタの形状を露出しながら、機能を旗の下で移行できるようにする。各抽出を独立して実行し、エラーのパスとヒュードレーションの動作を監視し、ロールバックルートを維持するまで、新しい所有権モデルが安定するまで。

CapacitorとElectronチーム向けには、Capgoが署名済みのJavaScript、CSS、構成、資産バンドルをターゲットチャンネルを通じて配信し、ロールアウトの制御、バージョン履歴、デバイスログ、採用率、失敗率のメトリクス、自動ロールバック保護を提供する。これにより、段階的に状態管理のリファクタリングを実行し、ネイティブの変更は関連するプラットフォームのリリースプロセスに従うことができる。オペレーショナルな利点は、移行の制御が可能であることであり、テストや互換性の計画をスキップする理由ではない。

ベストプラクティスと回避すべき一般的なミス

状態アーキテクチャは、レビューの質問が曖昧なまま残ると失敗する。設計レビューとプルリクエストで使用するチェックは次のとおり:

  • codeの所有者を名付けする Ask, “この値を変更できるモジュールはどれですか。また、APIはその境界をどのように強制するか?” ただし、現在2つのコンポーネントが同じフィールドを必要としているためだけに追加されたストアを却下します。
  • 定義された復旧契約: 各永続化された値について、リロード、再起動、アカウント切り替え、ログアウトで保存されるかクリアされるかを文書化します。作成中の請求書は再起動しても存続するかもしれませんが、選択されたワークスペースは再検証が必要になります。
  • 同期を観察できるようにする: リクエストIDをログに記録し、バージョン、リトライ回数、コンフリクト結果を記録します。リトライまたはコンフリクトの繰り返し失敗に警告を設定し、ユーザーが更新が欠落していることを報告する前に、ユーザーが報告する前にリトライまたはコンフリクトの繰り返し失敗に警告を設定します。
  • コマンドをレビューするのではなく、オブジェクトの形状をレビューする: 変化はビジネス上の意図を明確にし、入力を検証し、審査に適したアクション名を公開するべきです。直接書き込みがチェックを回避する場合は、レビューの議論に含めるべきです。
  • 攻撃的なシーケンスをテストする: リハイドレーションとナビゲーションを含むテストを実行し、書き込みが中断された後にリトライ、重複配信、オフラインの編集、2つのウィンドウからオフラインの編集、ログアウト中に保留中のリクエストを含むテストを実行します。
  • 削除パスを確認する: マイグレーションは不完全です。古いセレクター、永続化アダプター、イベントリスナーが元のストアに書き込む場合、古いセレクター、永続化アダプター、イベントリスナーが元のストアに書き込む場合にテストを追加します。

実行可能なPRの質問は、「このプロセスが書き込みを開始した後ですが、承認前に消滅した場合に何が起こるか?」 であることがよくあります。答えは、耐久性のあるデータ、リトライの所有権、重複排除、ユーザーに視覚化される失敗状態を含むべきです。

CapgoはCapacitorJSとElectronチームが、JavaScript、CSS、設定、資産の更新を、特定のチャネルを通じて、署名されたバンドル、ロールアウトの制御、デバイスレベルのログ、ロールバック保護とともに、提供します。使用 Capgo 状態管理のリファクタリングと復旧修正を段階的に提供し、ライフサイクルと同期テストで検証します。

Capacitor アプリの即時更新

When a web-layer bug is live, ship the fix through Capgo instead of waiting days for app store approval. Users get the update in the background while native changes stay in the normal review path.

マーティンから人間のサポート

Capgoは、プロフェッショナルなモバイルアプリを開発するために必要な最良の洞察を提供します。