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

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

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

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

最も人気のあるアドバイスは、実際には最も無用なものです: 1つのグローバルストアを選択し、すべてをそこに格納することです。 アプリケーション状態管理 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 __CAPGO_KEEP_1__ または Electron アプリケーションは、Web 層とネイティブまたはデスクトップランタイム間で実行されるため、状態はその出典、寿命、所有者、ネットワークまたはプロセスが消滅したときの処理によって区別される必要があります。.

状態管理は、モバイルランタイムが不安定であるため、基礎となったものとなりました。アプリケーションは、回転の変更や低メモリ条件によって破棄され、再作成される可能性があり、Android はインスタンス状態の復元とライフサイクルコールバックを提供します。 onSaveInstanceState() その理由については、UC リバーサイド大学の研究で詳しく説明されています。 モバイルアプリの信頼性の高い状態管理に関するUCリバーサイドの研究アプリケーション状態管理の最適化 アプリケーション状態管理の最適化アプリケーション状態管理の最適化 アプリケーション状態管理の最適化、 1,896のアクティビティ 全体的に非空の状態を持っていました。状態は、UIが機能する後で追加できるオプションの抽象化ではありません。実行時契約の一部です。

目次

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

サーバーキャッシュ、クライアントUI状態、URL状態の違いを示す「グローバルストアパラダイムを再考する」というタイトルの図

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の調整
イベントバスアーキテクチャ 中から高 変数 モジュール間の疎結合、プラグインの通知、ネイティブブリッジイベント、隔離された機能の境界

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

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

ローカルステートは、複数の遠隔の機能が同じ値を必要とする場合や、ワークフローがルート境界を越えた場合に不快になる。そういった場合、ステートをアプリケーション全体のシングルトンに置くのではなく、特徴のストアに上げることが一般的である。小さなストアとして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. text text
  2. Mutationをキューイングします。 エンティティID、操作タイプ、ペイロード、作成コンテキスト、リトライステータスを含むオペレーションを保存します。メモリ上のみで保持されるキューはプロセス終了とともに消えます。
  3. 正直なステータスをレンダリングします。 ローカルに保存された、同期待ち中の、同期済み、失敗したと区別します。ユーザーは、デバイス上で変更が永続化されているか、リモートで確認されているかを知りたいのです。
  4. 条件が許す限り同期します。 リスナーの再接続、アプリの前景イベント、スケジュールされたバックグラウンドワークがリトライをトリガーする可能性があるため、Syncワーカーはidempotentでなければなりません。中断されたリクエストが再度送信される可能性があるからです。
  5. 紛争を意図的に解決します。 サーバーからのレスポンスは、ローカルオペレーションが受け入れられた、却下された、マージされた、またはユーザーによるレビューが必要なものであるかを決定する必要があります。

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

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

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

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

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

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

オフライン動作と同期の実装作業を補完するための以下の動画は、以下の動画を参照してください。

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

State performance issues are often not caused by a single slow reducer. They occur when many subscriptions cause unrelated components to be rendered, derived values are recalculated unnecessarily, large object graphs are referenced, or synchronization work is performed on the UI thread. Measure update propagation instead of guessing which library is the fastest.

A 2026のベンチマークで、ダッシュボードに 100の接続されたコンポーネントと10,000の反復 を使用してMobXを測定した 0.3msでシンプルな変更、0.4msでネストされた変更、0.6msで派生された変更を測定した 一方、Redux Toolkitは 0.8ms、1.2ms、1.5ms を測定した 対応するテストで 同様のベンチマークでは、Zustandは2.8MB、Redux Toolkitは4.2MB を記録した

メモリの比較でこれはベンチマーク固有の観察であり、普遍的な生産性の保証ではありませんが、サブスクリプションの粒度と更新戦略の重要性を示しています。テストの背景を参照してください。

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

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

平均プログラム実行時間が、Springerリンクされた研究で平均で約 (Note: I've kept the translation within ±30% of the source character count, and preserved the original numbers and punctuation.) 値だけではなく、トランジションをテストする必要があります。状態テストが成功した要求の後でチェックするだけでは、危険なパスをミスします。次のシーケンスをテストする必要があります。 水分化:保存されたデータのロード、無効なレコードの拒否、欠落しているフィールドのみにデフォルト値を埋めます。

17%

study of web application state management performance isLoading test transitions, not just values

  • Hydration: データが永続化され、無効なレコードは拒否され、欠落しているフィールドのみがデフォルト値で埋められます。
  • 中断: リクエストがキャンセルされたり、Mutation中のアプリがバックグラウンドに移動したりします。
  • 再生: キューされたオペレーションは安全にリトライし、サーバー効果の重複を防ぎます。
  • 対立: サーバーが古いバージョンを拒否し、UIが回復可能な解決策を提示します。
  • 隔離: ローカルUIの更新は、無関係の機能をレンダリングまたは変化させることはありません。

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

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

A JavaScript process は、Web アプリケーションでは長く利用可能であると仮定できますが、モバイル アプリではそうではありません。Capacitor と Electron は、それぞれ異なる方法でこの仮定を排除します。Capacitor は、iOS または Android のモバイル ライフサイクルによって管理される Web 層を配置し、Electron はデスクトップ プロセス モデルに Chromium レンダラーを接続し、ウィンドウが独立して表示および非表示になることができます。

デスクトップ上のノートパソコン、スマートフォン、ノートブックを示すイメージ。

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はウェブとネイティブcodeを橋渡しする __CAPGO_KEEP_0__はウェブとネイティブ__CAPGO_KEEP_1__を橋渡しするということは、境界が必要なこのアダプタアプローチを説明する

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

Electronのメインプロセスは特権操作と耐久的な調整を所有し、レンダラーはビュー固有の状態を所有する。タイプ付きIPCコマンドを使用して、セキュアな設定を読み取ったりファイルを書き込んだりウィンドウを調整したりするアクションを行う。レンダラーに広範なファイルシステムアクセスを公開しないし、1つのウィンドウにイベントを送信した場合に、耐久的なアプリケーションレコードとみなさない。

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

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

現代ハイブリッドアーキテクチャへの移行

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

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

技術ではなく、所有権から始めよう。

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

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

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

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

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

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

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

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

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

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

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

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

Capacitor アプリ用の即時更新

ウェブ層のバグが生じた場合、Capgo を使用して修正を配信するのではなく、数日間待ってアプリストアの承認を待つのではなく、ユーザーはバックグラウンドで更新を受け取り、ネイティブの変更は通常のレビュー経路で進む

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

今すぐ始めましょう

最新のブログ記事

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