【実務・中級編】 状態管理ライブラリの選定基準と棲み分け – React実践ガイド

おい、最近どうだ?
「どの状態管理ライブラリを入れるべきか」でチームが二分して、朝会のたびに宗教戦争が勃発してないか?

「いや、今時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 (

);
};

どうだ? このコードの美しさと実用性が伝わったか?
無駄なボイラープレートがなく、TypeScriptの型推論もバッチリ効き、Redux DevToolsでのデバッグも一発だ。

—

5. まとめ:プロフェッショナルとしての判断軸

状態管理の選定に、絶対的な正解はない。あるのは「そのプロジェクトの規模、チームの習熟度、そして寿命に対するトレードオフの最適解」だけだ。

  • まずは React標準 (`useState` / `useContext`) でいけないか疑う。
  • サーバーのデータは React Query (TanStack Query) に任せる。
  • グローバルなUI状態をスッキリ軽く管理したいなら Zustand を採用する。
  • 大規模かつ厳格なルールでガチガチに固めたいなら Redux Toolkit を選ぶ。

流行り廃りに流されるな。アーキテクチャの本質を見極め、チーム全員が「メンテナンスしやすい」と胸を張って言える構成を選ぶのが、シニアエンジニアの仕事だ。

さて、理論はここまでだ。お前の次のスプリント、どの構成でいく?

コメント

タイトルとURLをコピーしました