おい、最近どうだ?
「どの状態管理ライブラリを入れるべきか」でチームが二分して、朝会のたびに宗教戦争が勃発してないか?
「いや、今時Reduxなんて冗長だろ、全部Zustandでいいじゃん」
「いやいや、大規模型ならRedux Toolkitの厳格さが必要だ」
「Recoil? まだメンテされてるのかそれ……」
気持ちはよく分かる。フロントエンドの生態系は移り変わりが激しい。新しく出てきたツールはどれも魅力的に見えるし、自分のプロダクトに銀の弾丸(Silver Bullet)をブチ込みたくなる衝動に駆られるのはエンジニアのサガだ。
だがな、シニアとしてハッキリ言わせてもらう。「流行りだから」「モダンだから」という理由で状態管理ライブラリを選ぶやつは、現場で確実に自爆する。
今日は、Reactの標準機能から、Zustand、Redux Toolkit(RTK)までを丸裸にし、プロジェクトの規模と要件に合わせた「冷徹で現実的な選定基準」を俺が叩き込んでやる。最後までついてきな。
—
1. そもそも状態管理ライブラリとは何のためにあるのか?
選定基準を語る前に、大前提を整理しよう。
状態管理ライブラリの存在意義は、突き詰めると「プロップドリル(Props Drilling)の地獄からの解放」と「不要な再レンダリングの最適化」の2点に尽きる。
Reactの `useState` と `useContext` は強力だが、これらを適当に組み合わせてアプリをスケールさせるとどうなるか?
「ボタンを1回クリックしただけで、アプリの半分以上のコンポーネントが再計算される」という、パフォーマンスのゴミカスみたいな状態が出来上がる。
ブラウザの裏側で何が起きてるか知ってるか?
Reactは、状態が更新されると「本当にそのツリーの末端まで描画し直す必要があるのか?」をVirtual DOMの差分検出で必死に調べる。この差分検出コスト(Reconciliation)は、コンポーネントのツリーが深ければ深いほど、CPUをゴリゴリ食い潰していくんだ。
だからこそ、「どこに、どのスコープで状態を置くべきか」を正しくデザインする必要がある。
—
2. 主要な選択肢のリアルな評価
現在、実務で検討される主な選択肢は以下の4つだ。それぞれの「ガチな評価」をしていこう。
① React 標準機能 (`useState` / `useReducer` + `useContext`)
- 向いているケース: 小規模アプリ、単一機能のウィジェット、プロップドリルが数階層程度で済む場合。
- 現場の評価: 侮るなかれ。最近のReactは進化している。むやみやたらと外部ライブラリを入れる前に、まずこれを疑うべきだ。
- 落とし穴: `useContext` は「それを購読しているコンポーネント全てを、値が変化した時に有無言わせず再レンダリングさせる」という致命的な特性を持つ。巨大なオブジェクトをContextに突っ込むのは、自爆テロと同じだ。
② Zustand
- 向いているケース: 中規模〜大規模アプリ。ボイラープレート(お決まりのコード)を極限まで減らし、素早く開発したいチーム。
- 現場の評価: 現在、僕が最も推すトレンドの急先鋒だ。 フックベースで書けて、Reactのコンポーネント外(通常のTSファイル内)からも状態を直接読み書きできる。この「どこからでも触れる手軽さ」が最高に心地いい。
- 落とし穴: 手軽すぎるゆえに、どこで誰が状態を書き換えているのか追いにくくなる「スパゲッティコード」の温床になりやすい。設計規律(Lintやディレクトリ構造のルール)がチームに必要。
③ Redux Toolkit (RTK)
- 向いているケース: 巨大なチーム、長寿命のEnterprise向けプロダクト、厳格な型安全性と「誰が書いても同じコードになる」予測可能性が必要な現場。
- 現場の評価: 「オワコン」だなんて言ったやつは誰だ? 冗長だった昔のReduxの面影は微塵もない。RTKなら Immer が内蔵されているので、直感的にイミュータブルな更新ができるし、TypeScriptとの相性も抜群だ。
- 落とし穴: 初期設定(Boilerplate)がどうしても重い。小〜中規模だと、明らかにオーバースペック(過剰品質)で開発速度が落ちる。
④ Recoil / Jotai (原子型 / Atomic State)
- 向いているケース: グラフ構造を持つ複雑な状態依存関係があるアプリ(例:高度なエディタ、ノードベースのUI)。
- 現場の評価: Jotaiは非常にエレガントだが、RecoilはMeta社のメンテナンス体制が怪しくなっており、実務での新規採用は個人的にストップをかけている。
—
3. 実務で迷わないための「状態管理選定 4つのマトリクス」
じゃあ、お前がアサインされたプロジェクトでどれを選ぶべきか。以下の基準で機械的にスパッと決めろ。
1. アプリの寿命とチームの人数は?
- 半年で終わるPoCや数人のチームなら → Zustand か React標準。
- 3年以上運用し、メンバーが10人以上入れ替わるなら → Redux Toolkit の堅牢さが恋しくなる。
2. 状態のスコープはどこまで必要か?
- 1つの画面(ページ)内で完結するなら → `useState` / `useReducer`
- アプリ全体で共有するグローバルなUI状態(サイドバーの開閉、ユーザーセッション等)なら → Zustand や RTK
3. サーバーの状態(APIレスポンス)とクライアントの状態をごちゃ混ぜにしていないか?
- ここが一番重要だ。APIから取ってきたデータは状態管理ライブラリに入れるな。 それは `TanStack Query (React Query)` や `SWR` の仕事だ。サーバーのキャッシュとUIのローカル状態を同一視する設計は、今すぐ爆破しろ。
—
4. 【コピペOK】現場で使えるZustandの実装パターン
百聞は一見にしかず。今どきのモダンなフロントエンド開発で最もコスパが良い「Zustand」を使った堅牢なストア構築のサンプルコードを見せてやる。
そのままエディタに貼り付けて、コメントを読み解いてくれ。
import { create } from ‘zustand’;
import { devtools, persist } from ‘zustand/middleware’;
// 1. 状態の型定義(TypeScriptの恩恵を最大限受けるため、厳格に定義する)
interface User {
id: string;
name: string;
email: string;
}
interface UserState {
user: User | null;
isLoading: boolean;
error: string | null;
// アクションの定義
setUser: (user: User | null) => void;
updateUserName: (name: string) => void;
logout: () => void;
}
// 2. ストアの作成(ミドルウェアとしてDevToolsとPersistを組み込む実務仕様)
export const useUserStore = create
devtools(
persist(
(set) => ({
user: null,
isLoading: false,
error: null,
// ユーザー情報をセットする
setUser: (user) => set({ user }),
// イミュータブルな更新をスッキリ書けるのがZustandの強み
updateUserName: (name) =>
set((state) => ({
user: state.user ? { …state.user, name } : null,
})),
// ログアウト(状態のクリア)
logout: () => set({ user: null, error: null }),
}),
{
name: ‘user-storage’, // localStorageに保存する際のキー名
// 必要に応じて partialize で保存するプロパティを絞り込むこと
partialize: (state) => ({ user: state.user }),
}
),
{ name: ‘UserStore’ } // Redux DevToolsで綺麗に名前が表示されるおもてなし
)
);
コンポーネント側でのスマートな呼び出し方
コンポーネント側で状態を使うときは、「必要なプロパティだけをセレクター経由で取得する」のが鉄則だ。これによって、無駄な再レンダリングを完全にハジキ返すことができる。
import React from ‘react’;
import { useUserStore } from ‘./userStore’;
export const UserProfile: React.FC = () => {
// 悪い例: const { user, logout } = useUserStore(); (これだと関係ない値の変化でも再レンダリングされる)
// 良い例: 必要な値だけをピンポイントで購読する(セレクターパターン)
const userName = useUserStore((state) => state.user?.name);
const logout = useUserStore((state) => state.logout);
if (!userName) {
return
ログインしていません。
;
}
return (
ようこそ、{userName} さん
);
};
どうだ? このコードの美しさと実用性が伝わったか?
無駄なボイラープレートがなく、TypeScriptの型推論もバッチリ効き、Redux DevToolsでのデバッグも一発だ。
—
5. まとめ:プロフェッショナルとしての判断軸
状態管理の選定に、絶対的な正解はない。あるのは「そのプロジェクトの規模、チームの習熟度、そして寿命に対するトレードオフの最適解」だけだ。
- まずは React標準 (`useState` / `useContext`) でいけないか疑う。
- サーバーのデータは React Query (TanStack Query) に任せる。
- グローバルなUI状態をスッキリ軽く管理したいなら Zustand を採用する。
- 大規模かつ厳格なルールでガチガチに固めたいなら Redux Toolkit を選ぶ。
流行り廃りに流されるな。アーキテクチャの本質を見極め、チーム全員が「メンテナンスしやすい」と胸を張って言える構成を選ぶのが、シニアエンジニアの仕事だ。
さて、理論はここまでだ。お前の次のスプリント、どの構成でいく?

コメント