状態管理
じょうたいかんり
画面に散らばる状態を一元管理し、UIの複雑さを予測可能に保つフロントエンド設計の要。
概要
状態管理(state management)は、フロントエンドアプリが抱える「状態」— 入力中のフォーム、開いているモーダル、ログイン中のユーザー、サーバから取得した一覧データなど — をどこに置き、どう更新し、どうUIへ届けるかを設計する分野です。画面は状態の写像なので、状態の置き方が乱れるとUIの乱れとして直接表面化します。
小さな画面なら各コンポーネントが自分の状態を持つだけで足ります。問題は SPA が育ったときです。同じデータ(未読件数、カート内容、ユーザー情報)を複数の画面部品が参照・更新し始めると、「誰がいつ書き換えたのか」「なぜここだけ古い値なのか」が追えなくなります。状態管理は、この複雑さを予測可能に保つための規律とライブラリ(Flux、Redux、Vuex/Pinia、Zustand など)の総称です。
なぜ生まれたか
きっかけとしてよく語られるのが、Facebook のチャット未読カウントのバグです。画面のあちこちに出る未読数がしばしば食い違い、直しても別の経路から再発する。当時主流だった双方向データバインディング(モデルとビューが互いを書き換え合う方式)では、ある変更が別のモデルを更新し、それがまた別のビューとモデルを連鎖的に書き換えるため、更新の波及経路がグラフ状に絡まり、原因の特定が困難でした。
Facebook はこの反省から2014年に Flux というアーキテクチャを発表します。骨子は「状態の更新経路を一方通行に固定する」こと。翌2015年にはこれを純化した Redux が登場し、「アプリ全体の状態を単一のストアに置き、純粋関数だけで更新する」スタイルが一世を風靡しました。状態管理という語彙が独立した設計分野として意識されるようになったのは、この Flux/Redux 以降です。
詳細
単方向データフロー — 更新経路を一方通行にする
Flux/Redux の中心にあるのが単方向データフローです。ビューは状態を直接書き換えられません。ユーザー操作は「何が起きたか」を表すアクション(例: { type: 'カートに追加', payload: 商品ID })として発行され、ストアに集約された状態は、リデューサ(現在の状態とアクションを受け取り新しい状態を返す純粋関数)だけが更新できます。新しい状態は購読しているビューへ流れ、画面が描き直される — この一巡が常に同じ向きで回ります。
この規律の見返りは追跡可能性です。状態が変わる経路はアクション→リデューサただ1本なので、アクションのログを見れば「いつ・何が・なぜ変わったか」が全部わかります。Redux DevTools の「タイムトラベルデバッグ」(過去の任意の時点の状態を再現する機能)が可能なのもこの構造ゆえです。描画側は仮想DOMやリアクティビティが状態→UIの反映を引き受けるため、開発者は状態の設計に集中できます。
イベントソーシングとの思想的つながり
「現在の状態は、過去に起きた出来事の列をリデューサで畳み込んだ結果である」という Redux の見方は、バックエンドのイベントソーシング(状態そのものではなく出来事の履歴を記録の主体にする設計)のフロントエンド版といえます。アクション=イベント、リデューサ=イベントの適用関数、タイムトラベル=リプレイに対応し、読み取り用に整形した派生状態(セレクタ)を分ける発想は CQRS にも通じます。フロントとバックで同じ思想が独立に有効だったことは、「変更を出来事として一本道に通す」設計の普遍性を示しています。
状態の分類 — すべてを同じ箱に入れない
現代の実務では、状態を性質で分類してから置き場所を決めるのが定石です。第一にローカル状態。入力中のテキストやアコーディオンの開閉など、1つのコンポーネントに閉じた状態で、コンポーネント自身が持てば十分です。第二にグローバル状態。ログインユーザーやテーマ設定など、画面をまたいで共有されるアプリ固有の状態で、ここが Redux や Zustand、Pinia といったストアの出番です。
第三がサーバー状態で、これは扱いが根本的に異なります。API から取得した一覧や詳細データは、真実がサーバ側にある「リモートデータのキャッシュ」であり、鮮度・再取得・楽観的更新・重複リクエストの排除といった固有の問題を持ちます。かつてはこれも Redux に詰め込んでいましたが、TanStack Query や SWR のようなサーバー状態専用ライブラリに任せる構成が主流になり、結果として「グローバルストアに残る状態は意外と少ない」ことが広く認識されました。
やりすぎ問題 — 何でもグローバルに置かない
Redux 全盛期の反省として語られるのが「やりすぎ」です。フォームの入力値からモーダルの開閉フラグまで何でも単一ストアに置いた結果、1つの入力欄のためにアクション定義・リデューサ・接続コードを書くボイラープレートが膨れ上がり、無関係な状態変更が広範囲の再描画を誘発しました。状態は使う場所の近くに置く(コロケーション)のが原則で、グローバル化は「本当に複数の離れた場所で共有・更新される」と確認できてからで遅くありません。
現在の潮流は、単一巨大ストアから「性質ごとの適材適所」への分散です。サーバー状態は専用ライブラリ、URLに置くべき状態(検索条件やページ番号)は URL そのもの、共有状態は小さなストアや signals、残りはローカルに。単方向データフローの規律という Flux の遺産は保ちつつ、箱を1つに限定しない — それが一巡りした先の現在地です。