KoderKoder.ai
料金エンタープライズ教育投資家向け
ログインはじめる

プロダクト

料金エンタープライズ投資家向け

リソース

お問い合わせサポート教育ブログ

リーガル

プライバシーポリシー利用規約セキュリティ利用ポリシー不正利用を報告

ソーシャル

LinkedInTwitter
Koder.ai
言語

© 2026 Koder.ai. All rights reserved.

ホーム›ブログ›なぜ状態管理はフロントエンドで最も難しい問題の一つなのか
2025年6月25日·1 分

なぜ状態管理はフロントエンドで最も難しい問題の一つなのか

状態管理が難しいのは、アプリが複数の真実のソース、非同期データ、UIのやり取り、パフォーマンスのトレードオフを同時に扱うからです。バグを減らすためのパターンを学びましょう。

なぜ状態管理はフロントエンドで最も難しい問題の一つなのか

フロントエンドアプリにおける「状態」が本当に意味すること

平易な定義

フロントエンドアプリでは、状態(state)とは簡単に言えばUIが依存し、時間とともに変化しうるデータです。

状態が変わると、画面はそれに合わせて更新されるべきです。画面が更新されなかったり、更新が不揃いだったり、古い値と新しい値が混在して表示されると、すぐに「状態の問題」を感じます—ボタンが無効のままになっている、合計が合わない、あるいはユーザーが行った操作を反映していないビューなどです。

日常でよく見る例

状態は小さなやり取りにも大きなやり取りにも現れます。例えば:

  • フォーム入力: ユーザーが入力した内容、チェックボックスの状態、表示すべきエラー
  • ナビゲーションの選択: 選択中のタブ、ウィザードの現在のステップ、展開/折りたたみの状態
  • ショッピング/カートのデータ: 商品、数量、適用されたクーポン、計算された合計
  • ユーザーセッション: ログイン中のユーザー情報、権限、機能フラグ、"ログインを維持する"設定

これらのうち一部は「一時的」なもの(選択中のタブのような)であり、他は「重要」に感じるもの(カートの中身など)です。どれも現在のUI描画に影響するため、状態と呼ばれます。

「コンポーネント内の変数以上」である理由

普通の変数はそれが存在する場所でしか意味を持ちません。状態はこれとは異なり、ルールが伴います:

  • 所有権: どの部分がそれを変更できるのか
  • 更新の流れ: いつどのように変更が再レンダリングを引き起こすのか
  • 一貫性: 複数のUI要素が同期を失わないようにすること

状態管理の本当の目標はデータを保存することではなく、更新を予測可能にしてUIを一貫させることです。「何が、いつ、なぜ変わったのか」に答えられるとき、状態は扱いやすくなります。それができないと、単純な機能でも予想外の挙動になります。

最初は簡単に感じるのに、突然難しくなる理由

プロジェクト開始時は状態管理はほとんど退屈に感じられることが多いです—良い意味で。コンポーネントがひとつ、入力がひとつ、更新も明快、という状況なら、ユーザーがフィールドに入力して値を保存し、UIが再レンダリングします。すべてが見通せて、即時的で、閉じています。

単純なケース: 1つのコンポーネント、1つの更新

テキスト入力が1つだけで、そのプレビューを表示するような例を想像してください:

  • 状態は入力をレンダリングしている同じコンポーネントにあります。
  • 更新はユーザーの操作に直接応じて起きます。
  • 「誰がデータを所有しているか」の議論はありません。

この構成では、状態は基本的に「時間とともに変化する変数」です。どこに保存され、どこで更新されるかを指し示せればそれで十分です。

ローカルコンポーネント状態が分かりやすい理由

ローカル状態がうまく機能するのは、心のモデルとコード構造が一致しているからです:

  • スコープが小さい(1つのコンポーネントか、せいぜい少数の子)
  • 更新はユーザーの観点からは同期的
  • データフローは明快: 入力 → 更新 → レンダー

Reactのようなフレームワークを使っていれば、アーキテクチャを深く考えなくても、デフォルトで十分なことが多いです。

アプリが成長すると何が変わるか

アプリが「ウィジェットのあるページ」から「プロダクト」になると、状態は1箇所に留まらなくなります。

同じデータが以下のように必要とされるようになります:

  • 複数の画面(ナビゲーション)
  • 離れたコンポーネント(共有UI)
  • リロードや再起動(永続化)
  • 複数ユーザー/デバイス(サーバー同期)

プロフィール名はヘッダーに表示され、設定ページで編集され、キャッシュに入って高速ロードに使われ、ウェルカムメッセージのパーソナライズにも使われる──このようになると、問題は「この値をどう保存するか」ではなく「どこに置けばどこでも正しく表示されるか」になります。

複雑さは線形に増えない

機能が増えるごとに状態の複雑性が徐々に増すわけではなく、段階的にジャンプします。

同じデータを参照する箇所が増えるだけで、調整の問題が生じます: ビューの一貫性を保つこと、古い値の防止、どちらが更新するのかの決定、タイミング処理など。共有する状態がいくつかあり、非同期作業が絡むと、個々の機能は単純に見えても全体としては理解しにくい挙動になることがあります。

真実のソースが多すぎる

同じ「事実」を複数箇所に保存してしまうと状態は厄介になります。各コピーがずれていき、UIが自分自身と争い始めます。

よくある候補

ほとんどのアプリは次のような複数の「真実の場所」を持ちます:

  • サーバーデータ(API/データベース): 正式な記録
  • クライアントキャッシュ(データ取得ライブラリのキャッシュなど): ローカルのミラーで、更新されるべきもの
  • ローカルUI状態(コンポーネントの状態): ユーザーが今やっていること
  • URL(パス、クエリパラメータ、ハッシュ): ブックマークや共有、復元が可能な状態

どれもある種の状態の所有者になり得ます。問題は、それらが同じ状態を所有しようとする時に生じます。

重複が起きる仕組み

よくあるパターン: サーバーからデータを取得してから、編集のためにローカルの状態にコピーする。例えば、ユーザープロフィールを読み込んで formState = userFromApi とする。その後、サーバーから再取得が走ったり(あるいは別のタブで更新が起きたり)すると、キャッシュとフォームの2つのバージョンが存在してしまいます。

重複は「便利そうな変換」を保存する際にも忍び込みます: items と itemsCount を両方保存したり、selectedId と selectedItem を両方持ったりする場合です。

認識しやすい症状

複数の真実のソースがあると、バグの現れ方は次のようになります:

  • 「この画面だけ動く」
  • ナビゲーションやリロード後にUIが一貫しない
  • あるコンポーネントでは正しいが別のコンポーネントでは古いデータが見える
  • 保存は成功するが一覧が更新されない(または二重に更新される)

経験則

各状態について、1つの所有者を選びましょう—更新が行われる場所を決め、その他は全て**射影(projection)**として扱います(読み取り専用、派生、あるいは一方向に同期されるもの)。所有者を指せない場合は、同じ事実を二重に保存している可能性が高いです。

非同期処理と副作用が状態を複雑にする理由

多くのフロントエンド状態は同期的で簡単に思えます: ユーザーがクリックして値をセットし、UIが更新される。しかし副作用はその綺麗な一連の流れを壊します。

副作用とは何か?

副作用はコンポーネントの純粋な「データに基づいてレンダリングする」モデルの外に手を伸ばすあらゆる動作です:

  • ネットワーク呼び出し(取得、保存、リトライ)
  • タイマーやデバウンス(setTimeout、interval)
  • サブスクリプション(WebSocket、イベントリスナー)
  • ブラウザストレージ(localStorage/sessionStorage)

どれも遅れて発火したり、失敗したり、何度も実行されたりします。

なぜ非同期の状態は同期より難しいのか

非同期更新は時間を変数として導入します。もはや「何が起きたか」ではなく「何がまだ起きているか」を考える必要があります。複数のリクエストが重なったり、遅いレスポンスが新しいレスポンスより後に到着したり、コンポーネントがアンマウントされた後にコールバックが状態を更新しようとすることがあります。

そのためバグはしばしば次のように見えます:

  • ローディングフラグが永遠に残る(エラーパスでクリアされない、あるいはリクエストがキャンセルされた)
  • UIが古いデータを一瞬表示する(キャッシュされた古い値が“最終”として見える)
  • 古いレスポンスが新しいものを上書きする(リクエストAがリクエストBより後に完了する)

シンプルな戦略: リクエストを明示的にモデル化する

isLoading のようなブールをUIに散らかす代わりに、非同期作業を小さなステートマシンとして扱いましょう:

  • idle(何も始まっていない)
  • loading(進行中)
  • success(データあり)
  • error(失敗)

データとステータスを一緒に追跡し、リクエストIDやクエリキーのような識別子を持って遅延レスポンスを無視できるようにすると、「今UIは何を表示すべきか」を推測ではなく明確に決められます。

UI状態とサーバー状態(似ているが違う)

多くの状態の頭痛は単純な混同から始まります: 「ユーザーが今やっていること」を「バックエンドが正しいと言っていること」と同じ扱いにしてしまうことです。両方とも時間とともに変化しますが、従うルールが異なります。

UI状態: インターフェースが今どう振る舞うか

UI状態は一時的で、インタラクション駆動です。ユーザーが期待するその瞬間の画面を描画するために存在します。

例: モーダルの開閉、アクティブなフィルタ、検索入力の下書き、ホバー/フォーカス、選択中のタブ、ページネーションUI(現在のページ、ページサイズ、スクロール位置)など。

通常、これらの状態はページまたはコンポーネントツリーに局所的です。ナビゲートするとリセットされるのは問題ないことが多いです。

サーバー状態: 取得したデータ(他で変わり得るもの)

サーバー状態はAPIからのデータです: ユーザープロファイル、商品リスト、権限、通知、保存された設定。これは「リモートの真実」であり、UIが何もしなくても別の誰かが編集したり、サーバーが再計算したり、バックグラウンドジョブが更新することがあります。

リモートであるため、ロード/エラー状態、キャッシュのタイムスタンプ、リトライ、無効化といったメタデータも必要になります。

混ぜると混乱する理由

UIの下書きをサーバーデータの中に入れると、再取得でローカル編集が消えることがあります。サーバーレスポンスをUI状態にルールなく保存すると、古いデータと戦ったり、重複フェッチが走ったり、画面が一貫しなくなります。

よくある失敗モード: ユーザーがフォームを編集している間にバックグラウンドで再取得が終わり、着信レスポンスが下書きを上書きしてしまう。

実用的な指針

サーバー状態はキャッシュパターン(フェッチ、キャッシュ、無効化、フォーカス時の再取得)で管理し、共有かつ非同期として扱いましょう。

UI状態はローカルコンポーネントの状態や、真に共有が必要なUI懸念のためのコンテキストで管理し、下書きは明示的に保存するまでサーバーに戻さないようにします。

派生状態と「計算できるものは保存しないで」ルール

コードはあなたのもののまま
実装を完全に制御する準備ができたらソースコードをエクスポートする。
コードをエクスポート

派生状態は他の状態から計算できる値です: ラインアイテムからのカート合計、元のリストと検索クエリからのフィルタ済みリスト、フィールド値とバリデーションからの canSubmit フラグなど。

こうした値を保存しておくのは便利に思えます(「total もステートに入れておこう」)。しかし、入力が複数箇所で変わるとドリフトのリスクが生じます: 保存された total がアイテムと一致しなくなったり、フィルタ済みリストが現在のクエリを反映しなくなったり、エラーを直した後も送信ボタンが無効のままだったりします。これらのバグは厄介で、個々のステート変数自体は正しいのに全体として矛盾するため検出が難しくなります。

セレクター/計算値を優先する

安全なパターンは、最小限のソースだけを保存し、他は読み取り時に計算することです。Reactでは単純な関数やメモ化された計算で十分なことが多いです。

const items = useCartItems();
const total = items.reduce((sum, item) =\u003e sum + item.price * item.qty, 0);

const filtered = products.filter(p =\u003e p.name.includes(query));

大規模アプリでは「セレクター」(または計算ゲッター)がこの考え方を体系化します: 1箇所で total、filteredProducts、visibleTodos を導出するロジックを定義し、すべてのコンポーネントが同じロジックを使うようにします。

派生値をキャッシュして良い場合

通常はレンダーごとに計算しても問題ないことが多いです。計算コストが実際に問題になる場合(高価な変換、大量のリスト、複数コンポーネントで共有される導出値など)にのみキャッシュしましょう。useMemo やセレクターのメモ化を使い、キャッシュキーが真に入力に依存することを確認してください—そうでないと、ドリフトに戻ってしまい、ただパフォーマンス目的で複雑さを増やしただけになります。

グローバル vs ローカル: 正しい所有者を選ぶ

状態が厄介になるのは、誰がそれを所有しているかが不明確なときです。

「所有権」が意味するもの

状態の所有者とは、その値を更新する権利を持つアプリ内の場所です。その他の部分は(props、コンテキスト、セレクターなどを通じて)読み取ることはできますが、直接変更すべきではありません。

明確な所有権は次の2つの問いに答えます:

  • 誰がこの値を更新できるか?(所有者)
  • 誰がこの値を読み取れるか?(消費者)

その境界が曖昧になると、競合する更新や「なぜこれが変わった?」という状況、再利用しにくいコンポーネントが生まれます。

グローバル状態: 便利だが結合が忍び込む

状態をグローバルストア(あるいはトップレベルのコンテキスト)に入れると、どこからでもアクセスできプロップドリリングを避けられるので一見きれいに見えます。しかしトレードオフは意図しない結合です—無関係な画面が同じ値に依存するようになり、小さな変更がアプリ全体に波及します。

グローバル状態は、現在のユーザーセッション、アプリ全体の機能フラグ、共有通知キューのような本当に横断的なものに適しています。

必要な範囲までだけ状態を持ち上げる(lift)

一般的なパターンは、まずローカルで始め、兄弟コンポーネント間で調整が必要になったら最小の共通親に状態を上げることです。

一つのコンポーネントだけが必要ならそのままローカルに保つ。複数のコンポーネントが必要なら最小限の共有オーナーにリフトする。多くの離れた領域が必要なら、そのときにグローバルを検討する、という具合です。

シンプルなヒューリスティック

使う場所の近くに状態を置き、共有が必要なときだけ上に持っていく。

これによりコンポーネントは理解しやすくなり、偶発的な依存が減り、将来のリファクタリングも安全に行いやすくなります。

同時実行性、レース、順序ずれ更新

危険な変更を元に戻す
手法が複雑になったらロールバックして、よりクリーンなソース・オブ・トゥルース設計を試す。
ロールバック

フロントエンドアプリは“単一スレッド”に見えますが、ユーザー入力、タイマー、アニメーション、ネットワークリクエストは独立して走ります。つまり複数の更新が同時に進行し、開始順と完了順が一致しないことがあります。

更新が衝突するとき

よくある衝突: UIの異なる部分が同じ状態を更新する場合。

  • 検索ボックスは入力ごとに query を更新する
  • フィルタのドロップダウンが(同じ結果リストに対して) query を更新する

個別には正しい更新でも、組み合わさるとタイミングによって上書きし合います。結果として、新しいフィルタを表示しているのに古い検索結果が表示される、といった食い違いが起きます。

レースコンディション: 早いユーザー、遅いネットワーク

レースはリクエストAを発行してすぐにリクエストBを発行したが、リクエストAの方が遅く戻ってきてしまうと発生します。

例: ユーザーが "c" → "ca" → "cat" と入力したとき、"c" のリクエストが遅く、"cat" のリクエストが早いと、UIが一度 "cat" の結果を表示した後に古い "c" の結果で上書きされることがあります。

バグは微妙で、すべてが「動いている」ように見えるが順序が間違っているだけです。

順序ずれバグを減らす手法

一般的には次のいずれかの戦略を使います:

  1. 新しいリクエストが来たら前のリクエストをキャンセルする(例えば AbortController を使う)
  2. 古いレスポンスを無視する(レスポンスが最新の入力に対応しているかチェックする)
  3. リクエストID/シーケンス番号を使い、最新のものだけ受け入れる

簡単なリクエストIDの例:

let latestRequestId = 0;

async function fetchResults(query) {
  const requestId = ++latestRequestId;
  const res = await fetch(`/api/search?q=${encodeURIComponent(query)}`);
  const data = await res.json();

  if (requestId !== latestRequestId) return; // stale response
  setResults(data);
}

楽観的アップデート( optimistic updates )と失敗するパターン

楽観的アップデートはUIを即時に感じさせますが、同時実行性が前提を壊すことがあります:

  • ユーザーがすばやく「いいね」を2回押す(いいね → 取り消し)したとき、リクエストが逆の順序で解決される
  • 在庫を楽観的に減らしてから後で失敗してロールバックが必要になったが、ユーザーはすでに別の操作をしている

楽観性を安全にするには明確な照合ルールが必要です: 保留中のアクションを追跡し、サーバーレスポンスは順序通りに適用し、ロールバックが必要なら既知のチェックポイントまで戻す(現在の見た目に基づいて戻すのではなく)といった方針です。

パフォーマンス: 状態変更が高コストになるとき

状態更新は「無料」ではありません。状態が変わると、アプリはどの画面部分が影響を受けるかを判断し、それを反映するための作業を行う必要があります: 値の再計算、UIの再レンダリング、フォーマット処理の再実行、場合によっては再取得や再検証。もしその連鎖が必要以上に大きければ、ユーザーは遅延やカクつき、ボタンが反応するまで待つような体験をします。

小さな変更が大きく感じられる理由

1つのトグル操作が不必要に多くの作業を引き起こすことがあります:

  • 実際には一部しか変わっていないのに大きなUIセクションが再レンダリングされる
  • リストが再描画/再計測され、スクロールがカクつく
  • オブジェクトや配列が毎回再作成され("deep churn")、差分検出が難しくなる

その結果は単なる技術的問題ではなく体験の問題です: タイピングが遅く感じる、アニメーションがカクつく、インターフェースが「もたついている」と感じられます。

よくあるパフォーマンストラップ

最も一般的な原因の1つは、状態が広すぎることです: 多くの無関係な情報をまとめた「大きなバケツ」オブジェクト。どれか1つのフィールドを更新するとバケツ全体が新しく見えるため、より多くのUIが目覚めてしまいます。

もう1つの罠は、計算で得られる値を状態に保存して手動で更新することです。これにより追加の更新(と余計なUI作業)が発生し、一貫性を保つための追加コストが生まれます。

UIを速く保つための戦術

状態を小さなスライスに分ける。 無関係な関心事を分離して、検索入力の変更でページ全体が更新されないようにする。

データを正規化する。 同じアイテムを複数箇所に保存するのではなく1箇所に保存して参照する。これにより重複更新が減り、1つの編集で多数のコピーを書き換えるような"change storm"を防げます。

派生値をメモ化する。 値が他の状態から計算できる場合(フィルタ済み結果など)、入力が実際に変わったときだけ再計算するようキャッシュします。

目標: 停滞を減らし、驚きを減らす

パフォーマンスを意識した状態管理の本質は封じ込めです: 更新は可能な限り小さな領域に影響させ、高価な処理は本当に必要なときだけ行う。そうなればユーザーはフレームワークを意識せず、インターフェースを信頼するようになります。

状態を推測ではなくデバッグ・テストする方法

状態のバグは個人的な問題のように感じられます: UIが「おかしい」けれど、「誰がその値をいつ変えたのか?」という最も簡単な問いにも答えられない。値がひっくり返った、バナーが消えた、ボタンが無効になったときには、推測ではなくタイムラインが必要です。

変更を追跡可能に(神秘的にしない)する

最速の明快化パスは予測可能な更新フローです。reducer、イベント、ストアのいずれを使うにせよ、次を満たすパターンを目指しましょう:

  • 変更は少数のよく名付けられたアクションを通じて起きる(ランダムなミューテーションではない)
  • 各アクションには明確なペイロードがある(setShippingMethod('express') のように)
  • アクションとその結果の状態遷移を一貫してログできる

明確なアクションログがあれば、デバッグは「画面を眺める」作業から「レシートを追う」作業に変わります。単純なコンソールログ(アクション名+主要フィールド)でも、起きたことを再構築するよりずっと役立ちます。

安定しているロジックをテストする

すべての再レンダをテストしようとするのではなく、純粋なロジックの部分をテストしましょう:

  • リデューサー/状態更新関数をユニットテスト: 前の状態+アクションで次の状態を検証
  • セレクター/派生計算のユニットテスト: 与えた状態から期待される出力を検証
  • 重要なユーザーフローの統合テスト: ログイン → データ読み込み → 編集 → 保存 → 確認を見る

この組み合わせにより「計算ミス」と実際の結線の問題の両方を検出できます。

非同期バグ用の軽い計測を追加する

非同期問題は隙間に隠れます。タイムラインを見える化するために最小限のメタデータを追加しましょう:

  • 重要な更新にタイムスタンプを付ける
  • リクエストID(アクションとレスポンスにIDを付与)

こうすれば、遅延レスポンスが新しいものを上書きしたときにすぐに証明でき、安心して修正に取りかかれます。

状態管理アプローチの選び方(ツール戦争は無視して)

プロトタイプの状態を高速に構築
チャットでReactアプリを作り、初日から状態モデルをシンプルに保つ。
無料で試す

状態ツールの選択は、ライブラリ比較の前に設計上の決定の結果として扱うと簡単です。どこが純粋にローカルで、何を共有する必要があり、何が実際に「サーバーデータ」であるかをマッピングしてからツールを比較しましょう。

選定基準(実用的な観点)

判断に役立つ制約は次のとおりです:

  • アプリの規模と寿命: 小さな内部ツールはシンプルなままで良いが、長期運用するプロダクトはより強い規約が役立つ
  • チームの習慣: チームが一貫して使えてレビューできるものを選ぶ
  • 非同期の必要性: 大量のフェッチ、キャッシュ、ページネーション、ミューテーションがあると選択肢が変わる
  • 状態の複雑さ: 複数ページにまたがるワークフロー、アンドゥ/リドゥ、多段フォームはより構造化が必要

高レベルな比較(イデオロギー抜きで)

  • Context + hooks: 依存注入や更新頻度の低い共有値(テーマ、認証情報)に向く。頻繁な更新には追加パターンが必要になることがある。
  • Redux風ストア: 強い規約、予測可能な更新、豊富なツール群。監査トレイルや複雑な調整が必要な場合に有利。
  • Atomストア(細粒度の状態): 多くのリデューサー配線を必要とせず、段階的にスケールさせやすい。共有状態を扱いやすい。
  • Queryキャッシュ(サーバー状態特化ツール): フェッチ、キャッシュ、重複除去、バックグラウンド再取得、ミューテーションに特化しており、多くの非同期の接着コードを減らしてくれる。

ツール優先で考えない

「Xを至る所で使う」と始めると、間違ったものを間違った場所に保存してしまいます。まずは所有権から: 誰が更新し、誰が読み、変化したときに何をするべきかを決めましょう。

組み合わせるのが最良であることが多い

多くのアプリはAPIデータ用にサーバー状態ライブラリを使い、クライアント専用のUI状態には小さなソリューションを使うことでうまくいきます。目的は明快さです: 各種類の状態が最も合理的に考えられる場所に存在すること。

Koder.ai の位置付け

状態の境界や非同期フローを反復しながら検討するなら、Koder.ai は実験のループを速く回せます。エージェントベースのワークフローでReactフロントエンド(とGo + PostgreSQLバックエンド)をチャットから生成できるため、ローカル vs グローバル、サーバーキャッシュ vs UI下書きといった所有モデルを素早くプロトタイプして、予測可能なものを採用できます。

状態を実験するときに役立つ2つの実用的機能: Planning Mode(構築前に状態モデルを概説する)とスナップショット+ロールバック("派生状態を削除する" や "リクエストIDを導入する" といったリファクタを安全に試して元に戻せる)。

状態を楽にするための実践チェックリスト

状態は設計の問題として扱うと楽になります: 誰が所有し、それが何を表し、どのように変わるかを決めるのです。コンポーネントが「謎めいている」と感じ始めたら、このチェックリストを使ってください。

1) 所有権と単一の真実を明確にする

問う: どの部分がこのデータに責任を持つのか? できるだけ使用箇所に近く配置し、複数箇所で本当に必要になったときだけリフトする。

  • 状態につき1つのオーナー
  • データは下に渡し、変更はコールバック/イベントで上に送る
  • 2箇所が同じ値を更新できるなら、それは真実のソースではなく衝突の素

2) 重複を避け、派生値をモデル化する

他の状態から計算できるものは保存しない。

  • 最小の入力だけ保存(例: items, filterText)
  • 出力(例: visibleItems)はレンダー時かメモ化で計算する

3) 非同期状態を明示化する(暗黙にしない)

非同期作業は明示的にモデル化すると分かりやすい:

  • 小さな「リクエスト状態」形を採用: status: 'idle' | 'loading' | 'success' | 'error' と data、error
  • loading や error を散らばったブールではなく一級のUI状態として扱う

4) よくあるアンチパターンに注意する

  • propsをstateにコピーしておく("念のため" はドリフトを生む)
  • すべてをグローバル化する(無関係な画面が結合する)
  • ブールスープ(isLoading, isFetching, isSaving, hasLoaded, …)ではなく単一のステータスを使う

5) 小さく安全なステップでリファクタする

  • 混ざった状態を分割する: UIの懸念(開閉や入力テキスト)はサーバーデータと分ける
  • 保存された派生値を削除し、本当のソースから計算するようにする
  • サイドエフェクト(フェッチやサブスクリプション)は機能ごとに1箇所に集める

実用的な目標

「どうしてこの状態になったの?」と問わなくて良い世界を目指しましょう。変更が5つのファイルを触らずに済むようになり、心のモデルとして1箇所を指して『ここが真実だ』と言えることを目標にしてください。

目次
フロントエンドアプリにおける「状態」が本当に意味すること最初は簡単に感じるのに、突然難しくなる理由真実のソースが多すぎる非同期処理と副作用が状態を複雑にする理由UI状態とサーバー状態(似ているが違う)派生状態と「計算できるものは保存しないで」ルールグローバル vs ローカル: 正しい所有者を選ぶ同時実行性、レース、順序ずれ更新パフォーマンス: 状態変更が高コストになるとき状態を推測ではなくデバッグ・テストする方法状態管理アプローチの選び方(ツール戦争は無視して)状態を楽にするための実践チェックリスト
共有
Koder.ai
Koderで自分のアプリを作ろう 今すぐ!

Koderの力を理解する最良の方法は、自分で体験することです。

無料で始めるデモを予約