コンテンツにスキップ

【比較】クライアント状態とサーバ状態管理の違い

概要

フロントエンドの「状態管理」は、よく一括りに語られますが、性質のまったく違う 2 つを含んでいます。クライアント状態(UI 状態/ローカル状態)は、その画面・そのアプリが所有する、同期的で一時的な状態——モーダルの開閉、フォーム入力中の値、選択中のタブなど。React なら useState、規模が大きければ Zustand や Redux で扱う領域です。サーバ状態は、自分が所有・管理していない場所にリモートで保持され、取得・更新に非同期 API を要し、自分の知らないところで他者に変更され得るため“古く(stale)”なり得る状態——TanStack Query は server state をこのように定義しています。

この 2 つは「解くべき問題」が違います。サーバ状態の本質はキャッシュ・同期・再検証(invalidation)・重複排除であり、TanStack Query/RTK Query/SWR といったライブラリはこの問題に特化した「データ取得&キャッシュツール」であって、汎用のグローバル状態ストアではありません。逆に UI のローカル状態はクライアント状態の手法で扱います。よくある事故は「サーバから取ったデータを Redux に詰めて、ローディング・再取得・キャッシュ無効化を手作業で管理してしまう」こと。本記事はこの混同を、各公式の定義に立ち返って解きほぐします。

例えるなら:自分の手元のメモと、図書館の蔵書

  • クライアント状態 … 自分の手元のメモ帳。書いた人=自分、いつでも即座に書き換えられ、閉じれば消える(一時的)。整合の心配はほぼ自分の中で完結。
  • サーバ状態 … 図書館の蔵書(の貸出状況)。所有者は自分ではなく、最新状況は問い合わせ(非同期)で確認するしかなく、他の利用者がいつのまにか借りていて、自分の手元の情報は“古く”なっている可能性がある。だから「コピーを手元に置く(キャッシュ)」「定期的に確認し直す(再検証)」という運用が要る。

詳細比較

比較軸 クライアント状態(UI/ローカル) サーバ状態(リモート)
所有者 そのアプリ/その画面(自分が所有) リモート。自分が所有・管理していない場所
同期/非同期 同期的(その場で読み書き) 非同期(取得・更新に async API が必要)
寿命 一時的(画面遷移やリロードで消えやすい) 永続的(サーバ側に残る/キャッシュは手元の写し)
鮮度(古くなるか) 基本ならない(自分が唯一の書き手) 古く(stale)なり得る(他者が知らぬ間に変更)
共有 通常そのクライアント内 複数クライアント間で共有され得る
主な関心事 UI の表現・遷移・入力中の値 キャッシュ・同期・再検証・重複排除
代表手法 useState/Zustand/Redux 等 TanStack Query/RTK Query/SWR
ツールの位置づけ 汎用の状態ストア データ取得&キャッシュツール(汎用ストアではない)
典型例 モーダル開閉、選択タブ、フォーム入力途中の値 API から取得した一覧・詳細、ユーザー情報

よくある誤解

  • 誤解1:「状態管理はぜんぶ Redux(や 1 つのグローバルストア)に入れれば良い。サーバから取ったデータも当然そこに入れる」 — これが典型的な事故です。RTK Query 公式は「データ取得とキャッシュは、状態管理とは別の関心事(a different set of concerns than state management)」と明言し、自身を「データ取得&キャッシュツールであり、手書きのデータ取得・キャッシュロジックを不要にする」ものと位置づけています。サーバデータを素の Redux に詰めると、ローディング状態・再取得・キャッシュ無効化・重複排除を全部手作業で書くことになり、その手書きロジックこそが RTK Query 等が排除しようとしている当のものです。
  • 誤解2:「TanStack Query / SWR は“Redux の代替(グローバル状態管理ライブラリ)”だ」 — 違います。TanStack Query 公式は、従来の状態管理ライブラリは「クライアント状態には優れているが、非同期/サーバ状態の扱いは得意でない」とし、自身を汎用ストアの置き換えではなくサーバ状態という特定問題を解くものと説明しています。SWR も「キャッシュ・再検証・リクエスト重複排除を備えた最小限のデータ取得 API」を掲げる stale-while-revalidate 系のデータ取得ライブラリです。UI のローカル状態(モーダル開閉など)はこれらの担当範囲ではありません。
  • 誤解3:「サーバ状態もクライアント状態も“同じ状態”だから、同じ道具・同じ作法で管理すればよい」 — 解くべき問題が違います。クライアント状態は自分が唯一の書き手で同期的・一時的、整合の心配はほぼ自己完結。サーバ状態は所有者が他にいて非同期で、他者により知らぬ間に変更され“古く”なり得る——だからキャッシュと再検証が本質になります(TanStack Query の server state 定義そのもの)。性質が違うものに同じ作法を当てると、片方では過剰、もう片方では不足になります。
  • 誤解4:「サーバ状態ライブラリを使うなら、クライアント状態ライブラリ(Zustand/Redux 等)は不要になる」 — 役割が違うので“どちらか”ではありません。サーバ状態ライブラリはリモートデータのキャッシュ/同期に特化し、UI のローカル状態(テーマ、選択中タブ、フォーム途中の値など)はカバーしません。実務では「サーバ状態は TanStack Query 等」「クライアント状態は useState/Zustand 等」の併用が標準的で、片方で全部を賄おうとすると無理が出ます。

実務での選び分け

  • モーダル開閉・選択タブ・フォーム入力中の値など、その画面が所有する一時的状態 → クライアント状態。まず useState、広く共有が要れば Zustand/Redux など。
  • API から取得する一覧・詳細・ユーザー情報など、リモート所有で古くなり得るデータ → サーバ状態ライブラリ(TanStack Query/RTK Query/SWR)。キャッシュ・再検証・重複排除を任せ、手書きしない。
  • 「サーバデータを Redux に入れてローディングや再取得を自前管理している」 → リファクタの合図。サーバ状態はサーバ状態ライブラリへ移し、Redux は本来のクライアント状態に絞る。
  • どちらか迷ったら所有者を問う →「このデータの“正”はどこにある? 自分のアプリの中なら→クライアント状態。リモート(API/DB)で他者も触れるなら→サーバ状態」。
  • 両方使うのが普通 → 「サーバ状態=専用ライブラリ」「クライアント状態=useState/Zustand 等」を併用。どちらか一つで全部を賄おうとしない。
  • エコシステムは動く前提 → 具体的なライブラリ選定や API はバージョンで変わるため、導入時は各公式ドキュメント(TanStack Query/RTK Query/SWR)の最新版を確認する。区別の考え方そのものは安定している。

ひとことまとめ

クライアント状態は「そのアプリが所有する、同期的・一時的な UI 状態(useState/Zustand/Redux の領分)」、サーバ状態は「リモート所有で非同期・共有され、古くなり得るデータ(TanStack Query/RTK Query/SWR がキャッシュ・同期・再検証で扱う領分)」。サーバ状態ライブラリは汎用グローバルストアの代替ではなく、両者は併用が標準——“サーバデータを素の Redux で手作業管理”を避けるのが要点です。

出典・参考

  • TanStack Query 公式ドキュメント「Overview」(server state=リモート所有・非同期 API 必須・他者に変更され得る・"out of date"(stale)になり得る/従来の状態管理はクライアント状態には強いが非同期/サーバ状態は不得意): https://tanstack.com/query/latest/docs/framework/react/overview
  • Redux Toolkit 公式ドキュメント「RTK Query Overview」(データ取得&キャッシュツール、手書きのデータ取得・キャッシュロジックを不要にする、"a different set of concerns than state management"): https://redux-toolkit.js.org/rtk-query/overview
  • SWR 公式サイト(stale-while-revalidate、キャッシュ・再検証・リクエスト重複排除を備えた最小限のデータ取得 API、フォーカス/ネットワーク復帰時の再検証): https://swr.vercel.app/
  • React 公式ドキュメント「Managing State」/「useState」(コンポーネント内のローカル状態を宣言・更新する基本 API。UI ローカル状態の扱い): https://react.dev/learn/managing-state