SPAで状態管理ライブラリが必要になった背景

SPAで状態管理ライブラリ(Redux等)が必要になった歴史的背景

ReduxやVuexのような状態管理ライブラリは、最初に触れると「なぜこんなに大げさな仕組みが必要なのか」と感じがちです。小さな画面ならローカル変数やコンポーネントのstateだけで動きます。

それでも多くのSPAが最終的に状態管理へ進むのは、SPAでは“画面”ではなく“アプリケーション”を作ることになるからです。

この違いが、設計の難しさの正体です。

MPA時代の状態はどこにあったか

従来のWebアプリ(MPA)では、ページ遷移のたびにサーバがHTMLを返していました。つまり画面の状態はサーバにありました。

  • カート内容
  • ログイン状態
  • 検索条件

これらはセッションに保存され、ページを開くたびに再描画されます。ブラウザは単なる表示装置です。ページ遷移は「状態のリセット」でもありました。

そのためフロントエンドは複雑な状態管理を必要としませんでした。

SPAで起きた変化

SPAではページ遷移がありません。アプリは1ページ上で動き続けます。つまり状態がブラウザに残り続けます。

ここで次の問題が発生します。

  • ユーザー情報をどこに保持するか
  • API結果をどこに保存するか
  • 別画面へどう引き継ぐか

例えば商品一覧画面から詳細画面へ移動したあと、戻ったときにスクロール位置や検索条件を維持したい場合があります。これは「画面の状態」が必要です。

つまりSPAでは、サーバが持っていた状態をブラウザが持つようになりました。

コンポーネント分割が新たな問題を生む

ReactやVueでは、UIを小さなコンポーネントに分割します。これは再利用性を高めますが、副作用があります。

親コンポーネントが取得したデータを、深い子コンポーネントへ渡す必要が出ます。

<App>
  <Layout>
    <Sidebar>
      <UserMenu />
    </Sidebar>
  </Layout>
</App>

ログインユーザー情報をUserMenuで表示したい場合、上位からpropsを渡し続けます。これを「props drilling」と呼びます。

コンポーネント階層が深くなるほど、関係ない中間コンポーネントまで修正が必要になります。

状態の分散と不整合

さらに難しい問題が起きます。同じデータを複数箇所で保持し始めることです。

  • ヘッダにユーザー名
  • プロフィール画面にもユーザー名
  • 設定画面にもユーザー情報

どこかで更新したとき、他が更新されない現象が起きます。これが「状態の不整合」です。

SPAのバグの多くは描画処理ではなく、この同期失敗から発生します。

状態管理ライブラリの役割

Reduxのようなライブラリは、状態を一箇所に集約します。

  • グローバルストアを用意
  • 更新は必ずアクション経由
  • 全コンポーネントが参照可能
dispatch({ type: "USER_UPDATE", payload: user });

これにより、どの画面から更新しても同じデータが反映されます。状態の「唯一の真実の場所(Single Source of Truth)」を作るのが目的です。

なぜ最初は不要に見えるのか

小規模アプリでは状態が少なく、問題が顕在化しません。しかし機能が増えると次が起きます。

  • ローディング状態の管理
  • エラー状態の管理
  • キャッシュの再利用
  • 同時更新

この段階でローカルstateでは破綻します。後から導入すると大規模改修になります。

注意点:状態管理は万能ではない

Reduxを入れるとコード量は増えます。単純なフォーム中心アプリでは過剰設計になることもあります。

最近はReact Queryのように「サーバ状態」と「UI状態」を分ける考え方も広がっています。すべてをグローバルにする必要はありません。

よくある誤解

状態管理はパフォーマンス改善のためではありません。主目的は整合性です。描画速度より、バグの発生率を下げるための仕組みです。

まとめ

SPAで状態管理ライブラリが必要になる背景は、ページ遷移がなくなったことでアプリケーションが長時間動き続けるようになったためです。状態の寿命が伸び、複数画面で共有されるようになりました。

サーバが担っていた「状態の一元管理」をブラウザ側で再現する仕組みが状態管理ライブラリです。Reduxは複雑さを増やす道具ではなく、複雑さを制御するための枠組みと捉えると理解しやすくなります。