React状態管理の深掘り #1 クライアント状態とサーバー状態の違い

読了 7分

React基礎講座(#1〜#15)では、useStateuseReducer状態の引き上げuseContextまで扱いました。小さなアプリなら、これらの組み込みツールだけで十分です。ところがアプリが大きくなると、useStateuseContextだけでは手に余る地点が必ず来ます。このシリーズは、まさにその地点から始まります。

全6回で構成します。

  • #1 クライアント状態とサーバー状態の違い ← この記事
  • #2 TanStack Queryで扱うサーバー状態
  • #3 Zustandで扱う軽量なクライアント状態
  • #4 Jotaiと原子(atom)モデル
  • #5 Redux Toolkitとレガシーの文脈
  • #6 どのツールをいつ使うか — 決定ガイド

この記事はコードがほとんどありません。ツールを選ぶ前に、状態には性質の異なる2種類があるという事実を先に整理しておくことが、残り5回を理解するうえで決定的だからです。

組み込みツールが手に余る瞬間 #

Reactが標準で提供する状態ツールは3つです。

  • useState — コンポーネント1つのローカル状態
  • useReducer — 遷移ルールが複雑なローカル状態
  • useContext — コンポーネントツリーを横断して値を渡す

アプリが大きくなると、これらのツールで2つの異なる問題を同時に解こうとするようになります。そして、その2つの問題は性質がまったく違います。

問題A。 ダークモード設定、サイドバーの開閉、モーダルの表示状態、カートに入った項目のように、ブラウザの中で生まれ、ブラウザの中だけで意味を持つ値を、複数のコンポーネントで共有する必要があります。useContextで解けますが、値が変わるたびにProvider配下の全体が再レンダリングされやすく、Contextが増えるとProviderのネストが深くなります。

問題B。 ユーザー一覧、投稿、注文履歴のように、サーバーのデータベースに本物の原本があり、画面はその複製を見せているだけの値を扱う必要があります。useEffectの中でfetchし、useStateに入れるおなじみのパターンですが、ローディング表示、エラー処理、再取得、キャッシュ、他の画面との同期をすべて手で書いていくと、コードはたちまち複雑になります。

核心はこうです。問題Aと問題Bは同じuseStateで扱いますが、実はまったく種類の異なる状態です。 この2つを区別できないと、ツール選びがずっとずれ続けます。

クライアント状態とサーバー状態 #

状態を次の2種類に分けてみます。

区分クライアント状態サーバー状態
原本のある場所ブラウザ(この状態が原本そのもの)サーバーのDB(画面は複製)
ダークモード、モーダルの開閉、フォーム入力、選択中のタブユーザー一覧、投稿、注文履歴、検索結果
同期の要否不要(自分のブラウザ内で完結)必要(サーバーと合わせ続ける)
鮮度常に最新(自分が変えるので)時間が経つと古くなる(他人が変えうる)
主な関心誰が共有しどう更新するかいつ再取得しどうキャッシュするか

クライアント状態は、ブラウザの中で生まれ、ブラウザの中で消えます。ダークモードを点けたという事実を、サーバーに尋ねる必要はありません。この状態自体が原本です。

サーバー状態は違います。画面に見えている投稿一覧は本物ではなく、サーバーにある原本の複製です。自分が見ている間に、他のユーザーが投稿を追加したかもしれません。つまりサーバー状態は放っておくと古くなります。 そこから「いつ再取得するか」「取得したものをどれくらいキャッシュするか」「複数の画面が同じデータを見ているとき、どうやって一斉に更新するか」といった関心が付いてきます。クライアント状態にはない関心です。

注記
この区別は、TanStack Queryを作ったTanner Linsleyが広めた観点です。「サーバー状態」という名前は最初こそ違和感があるかもしれませんが、一度このレンズでコードを見ると、これまでuseEffect + useStateで苦労して書いていたものの正体がはっきりします。

なぜこの区別がツール選びを変えるのか #

2つの状態は性質が違うので、よく合うツールも違います。

サーバー状態のためのツールは、キャッシュ、自動再取得、ローディングとエラー状態、バックグラウンド更新を標準で備えているべきです。この領域の標準がTanStack Query(旧名React Query)です。#2で扱います。

クライアント状態のためのツールは、軽くグローバルな値を共有し、必要なコンポーネントだけ再レンダリングされるべきです。この領域には選択肢が複数あります。Zustand(#3)、Jotai(#4)、そして伝統のRedux Toolkit(#5)を順に見ていきます。

ここで最もよくある間違いを先に挙げておきます。サーバーデータを、ReduxやZustandのようなグローバルクライアント状態ストアに丸ごと入れることです。しばらく標準のように使われましたが、こうするとキャッシュ、再取得、鮮度管理をすべて手で作り直すことになります。サーバー状態はサーバー状態ツールに任せるほうが、はるかにシンプルです。

よくあるアンチパターン vs 推奨構造
[よくあるアンチパターン]
すべての状態 → 1つの巨大なグローバルストア(Reduxなど)
  - ダークモードもここに
  - サーバーから受け取った投稿一覧もここに
  - キャッシュ・再取得・ローディング処理をすべて手で実装

[推奨構造]
サーバー状態   → TanStack Query(キャッシュ・再取得・鮮度を自動)
クライアント状態 → Zustand / Jotai / Context(軽量なグローバル共有)
ローカル状態   → useState / useReducer(そのまま)

ではuseStateはいつ使うのか #

誤解を防ぐために、はっきりさせておきます。このシリーズはuseStateを捨てようという話ではありません。 1つのコンポーネントの中だけで使う状態、たとえばフォームの入力値やトグル1つは、依然としてuseStateが正解です。新しいツールは、次の条件が重なるときに必要になります。

  • 複数のコンポーネントが同じ状態を共有する必要があります。
  • そのコンポーネントがツリー上で遠く離れていて、propsで渡すのが面倒です。
  • (サーバー状態なら)キャッシュと再取得まで気にする必要があります。

これに当てはまらなければ、組み込みツールで十分です。ツールを増やすこと自体がコストだという点を、忘れないでください。

このシリーズで学ぶこと #

各回は、ツール1つを同じ基準で見ていきます。インストール、基本的な使い方、再レンダリングの挙動、どんな状況に合うかを、一貫して比較できるように構成しました。

  • #2 TanStack Query — サーバー状態のキャッシュと再取得を自動化
  • #3 Zustand — 最小限のボイラープレートでグローバルなクライアント状態
  • #4 Jotai — 状態を小さな原子に分割するボトムアップモデル
  • #5 Redux Toolkit — 今も多くのコードベースに残る標準の現在の姿
  • #6 決定ガイド — 状況別に何を選ぶか

まとめ #

この記事の結論は一文です。状態にはクライアント状態とサーバー状態があり、両者は性質が異なるので、よく合うツールも異なります。

  • クライアント状態 — ブラウザの中で完結、それ自体が原本
  • サーバー状態 — サーバーに原本がある複製、放っておくと古くなる

新しい画面を作るたびに、自問してください。「この値はブラウザの中で完結するのか、それともサーバーデータの複製なのか?」 この1つの問いが、残りの回でどのツールを取り出すかを決めます。

次回「React状態管理の深掘り #2 TanStack Queryで扱うサーバー状態」では、まずサーバー状態から扱います。useEffect + useStateで書いていたデータ取得が、どうやって数行に縮むのかを実際に見ていきます。

X