【2025年3月版】Reactの状態管理「useState」「Redux Toolkit」「Jotai」「Zustand」を比較してみた
React開発において、状態管理の選定はアプリケーションの保守性やスケーラビリティを左右する重要な要素です。
2025年現在、さまざまな状態管理ライブラリが登場しており、それぞれに特徴と使いどころがあります。
今回は特に人気と実績のある「useState」「Redux Toolkit」「Jotai」「Zustand」の4つに絞り、実際のコード例を交えながら比較・解説いたします。
※本記事では、Recoilは2025年1月2日にリポジトリがアーカイブされたため、比較対象から除外しております。
最小構成で使えるuseStateの柔軟さ
useStateはReactに組み込まれている最も基本的な状態管理手法です。
追加ライブラリが不要で、小規模な状態であればこれ一つで十分です。
tsximport { useState } from "react";
export function Counter() {
const [count, setCount] = useState(0);
return (
<>
<p>現在のカウント: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</>
);
}
特徴と適用範囲
| 特徴 | 内容 |
|---|---|
| 標準機能 | Reactに標準で組み込まれている |
| シンプル | 学習コストが低く、すぐに使える |
| スケーラビリティ | 複雑な状態になると管理が煩雑になることがある |
公式ドキュメント:useState – React Docs
グローバル管理と非同期処理に強いRedux Toolkit
Reduxは長年使われてきたグローバル状態管理の定番ですが、記述が冗長であるという課題がありました。
その課題を解消したのが公式のRedux Toolkitです。
ts// counterSlice.ts
import { createSlice } from '@reduxjs/toolkit';
export const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: state => {
state.value += 1;
},
},
});
export const { increment } = counterSlice.actions;
export default counterSlice.reducer;
ts// store.ts
import { configureStore } from '@reduxjs/toolkit';
import counterReducer from './counterSlice';
export const store = configureStore({
reducer: {
counter: counterReducer,
},
});
tsx// Counter.tsx
import { useSelector, useDispatch } from 'react-redux';
import { increment } from './counterSlice';
export function Counter() {
const count = useSelector((state: any) => state.counter.value);
const dispatch = useDispatch();
return (
<>
<p>Redux Count: {count}</p>
<button onClick={() => dispatch(increment())}>+1</button>
</>
);
}
特徴と適用範囲
| 特徴 | 内容 |
|---|---|
| 非同期処理対応 | createAsyncThunkによるAPI通信の管理が簡潔 |
| DevTools対応 | Redux DevToolsによるデバッグが可能 |
| 分離性 | store、slice、reducerの役割が明確で大規模でも管理しやすい |
公式ドキュメント:Redux Toolkit | Redux
シンプルで直感的なAtomベースのJotai
JotaiはAtomic(原子)状態に基づいた新しい状態管理アプローチを提供します。
必要なデータだけをAtomとして定義でき、スケーラブルでパフォーマンスにも優れています。
ts// atoms.ts
import { atom } from 'jotai';
export const countAtom = atom(0);
tsx// Counter.tsx
import { useAtom } from 'jotai';
import { countAtom } from './atoms';
export function Counter() {
const [count, setCount] = useAtom(countAtom);
return (
<>
<p>Jotai Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</>
);
}
特徴と適用範囲
| 特徴 | 内容 |
|---|---|
| アトム単位管理 | 状態を細かく分割できるため、再レンダリングが最小限で済む |
| 型安全 | TypeScriptとの親和性が高い |
| SSR対応 | React 18以降のStreaming対応も可能 |
公式ドキュメント:Jotai Documentation
ミニマルでパワフルなZustandの柔軟性
ZustandはReduxのようなFluxアーキテクチャに縛られず、関数型で定義できる状態管理ライブラリです。
コード量が少なく、ボイラープレートが圧倒的に少ないのが魅力です。
ts// useStore.ts
import { create } from 'zustand';
type State = {
count: number;
increment: () => void;
};
export const useStore = create<State>((set) => ({
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
}));
tsx// Counter.tsx
import { useStore } from './useStore';
export function Counter() {
const { count, increment } = useStore();
return (
<>
<p>Zustand Count: {count}</p>
<button onClick={increment}>+1</button>
</>
);
}
特徴と適用範囲
| 特徴 | 内容 |
|---|---|
| ボイラープレートなし | Reduxより簡潔に書ける構文 |
| 非同期対応 | 状態ロジック内で直接async関数を利用可能 |
| 型の拡張性 | TypeScriptとの相性も良く、型安全に管理可能 |
公式ドキュメント:Zustand – Bear necessities for state management
実戦パターン:現場でよく使われる構成例
現場ではライブラリの選定だけでなく、どのように組み合わせて構築するかも重要です。
以下に、各ライブラリが実際にどのような形で導入されているかのパターンを整理いたします。
| ライブラリ | よく使われる構成パターン例 |
|---|---|
| useState | useEffectと組み合わせたフォーム制御、useReducerと併用して複雑なローカル状態の分離管理 |
| Redux Toolkit | slice + thunk + RTK Queryの組み合わせでREST API完全管理、Next.jsとの連携も容易 |
| Jotai | Provider + atomFamilyによる動的状態の生成、React Suspenseとの統合による非同期データ制御 |
| Zustand | create + persist + middlewareで永続化やロギング機能を拡張、特にNext.js・Viteと好相性 |
たとえばJotaiでは以下のように非同期読み込みを自然に表現できます。
ts// atoms.ts
import { atom } from 'jotai';
import { atomWithQuery } from 'jotai-tanstack-query';
export const userAtom = atomWithQuery(() => ({
queryKey: ['user'],
queryFn: () => fetch('/api/user').then(res => res.json()),
}));
パフォーマンス比較:レンダリングと効率性
状態管理ライブラリの選定ではレンダリング最適化も重要なポイントです。
とくに再レンダリングの影響範囲が広いと、ユーザー体験やパフォーマンスに直結します。
| ライブラリ | パフォーマンス特性 |
|---|---|
| useState | 状態の粒度が小さいため再レンダリング制御は可能だが、共有が困難で冗長になりやすい |
| Redux Toolkit | connectで最適化できるが、useSelector多用時に再レンダリング範囲が広くなることがある |
| Jotai | Atom単位で依存関係を追跡、再レンダリング範囲が極小。SuspenseやReact.memoとの相性良好 |
| Zustand | selectorやshallowで再レンダリングを極限まで抑制できる。特にUIの反応性が高い |
Zustandでは以下のようにshallowを利用してパフォーマンスを向上させられます。
tsimport { useStore } from './store';
import shallow from 'zustand/shallow';
const Component = () => {
const [count, increment] = useStore(
(state) => [state.count, state.increment],
shallow
);
};
テストとの親和性:扱いやすさとモックのしやすさ
状態管理はユニットテストやE2Eテストにおける再現性や信頼性にも関係してきます。
それぞれのライブラリのテストしやすさを比較します。
| ライブラリ | テスト観点での評価 |
|---|---|
| useState | ローカルな関数単位で完結するためユニットテストが容易 |
| Redux Toolkit | StoreやMiddlewareをmockStoreで再現でき、Jestでのテストが体系化されている |
| Jotai | Atomを直接Mockでき、Providerのスコープで状態を制御しやすい |
| Zustand | Storeを直接差し替え可能。状態の初期化や書き換えも柔軟でテストシナリオ構築が簡単 |
Zustandのテスト例:
tsimport { useStore } from './store';
beforeEach(() => {
useStore.setState({ count: 100 });
});
it('初期状態が正しく設定されているか', () => {
expect(useStore.getState().count).toBe(100);
});
Redux ToolkitではMockStoreを使った統一的なテストが可能です。
tsimport configureMockStore from 'redux-mock-store';
import thunk from 'redux-thunk';
const middlewares = [thunk];
const mockStore = configureMockStore(middlewares);
it('アクションが正しくディスパッチされるか', () => {
const store = mockStore({ counter: { value: 0 } });
store.dispatch(increment());
const actions = store.getActions();
expect(actions[0].type).toBe('counter/increment');
});
補足:公式ドキュメントリンクまとめ
| ライブラリ | リンク |
|---|---|
| useState | https://react.dev/reference/react/useState |
| Redux Toolkit | https://redux-toolkit.js.org/ |
| Jotai | https://jotai.org/ |
| Zustand | https://zustand-demo.pmnd.rs/ |
まとめ
2025年現在、Reactの状態管理には多彩な選択肢が存在しております。
それぞれに向き・不向きがあり、以下の観点から適切なライブラリを選択することが重要です。
状態管理ライブラリ選定の5つの視点
| 観点 | チェックポイント例 |
|---|---|
| 状態のスコープ | 局所的な状態か、アプリ全体で共有すべきグローバル状態か |
| 非同期処理の必要性 | APIとのやりとりをどのように扱うか、thunkやsuspenseなどが必要か |
| プロジェクトの規模 | 小規模ならuseState、中〜大規模ならRedux ToolkitやZustandが適する |
| パフォーマンス効率 | 再レンダリングを最小限に抑えたいならJotaiやZustandが非常に有効 |
| テストのしやすさ | 状態のMockや再現性の高いテストを重視するならRedux ToolkitやZustandが柔軟に対応可能 |
状態管理ライブラリ早見表
| ライブラリ | スコープ | 非同期 | 実戦パターン | パフォーマンス | テストのしやすさ |
|---|---|---|---|---|---|
| useState | ローカル | △ | useReducer併用の局所構成 | 良(粒度次第) | 非常に良い |
| Redux Toolkit | グローバル | ◎ | slice + thunk + RTK Queryによる構造的構成 | 中(最適化次第) | モック・DevTools対応 |
| Jotai | 柔軟 | ◯ | atomFamily + Suspenseによる非同期制御 | 非常に良い | Providerで切替可能 |
| Zustand | グローバル | ◎ | create + persist + middleware構成が人気 | 非常に良い | 柔軟な状態初期化可 |
結論としては、以下のように選ぶと良いでしょう:
- 小規模・シンプルなアプリ:
useStateやuseReducerで十分 - 非同期処理が中心:
Redux ToolkitやJotai - パフォーマンス最優先:
ZustandかJotai - テスト重視・エンタープライズ対応:
Redux ToolkitかZustand - 柔軟な開発体験を求める:
ZustandやJotaiが直感的でおすすめ
今後もReactエコシステムの進化に合わせ、最適な状態管理戦略を選び続けることが、プロダクトの品質と開発体験を高める鍵となります。
本記事が、皆さまの状態管理選定と実装において、少しでも参考になれば幸いです。
article伝搬方式を比較:Zustand の selector/derived-middleware/外部 reselect の使い分け
articleZustand subscribeWithSelector で発生する古い参照問題:メモ化と equalityFn の落とし穴
articleZustand × useTransition 概説:並列レンダリング時代に安全な更新を設計する
articleフィーチャーフラグ運用:Zustand で段階的リリースとリモート設定を実装
articleオフラインファースト設計:Zustand で楽観的 UI とロールバックを実現
articleZustand Selector パターン早見表:equalityFn/shallow/構造的共有の勘所
articleJotai でフォームを分割統治:フィールド粒度の atom 設計と検証戦略
articleJotai 非同期チートシート:async atom/loadable/suspense の使い分け
articlejotai × TypeScript 型推論を極める実戦のための環境設定術
articleJotai のリアクティブ思考法:コンポーネントから状態を切り離す設計哲学
articleJotai 運用ガイド:命名規約・debugLabel・依存グラフ可視化の標準化
articleJotai のスケール特性を実測:コンポ数 × 更新頻度 × 派生深さのベンチ
articleJest でストア(Zustand・Redux)の状態をテストする
articleRedux Toolkit から Jotai への移行は可能か?具体的なリファクタリング戦略とコード例
articleJest で Redux のロジックをテストする手法
articleZustandとReduxは何が違う?Zustandの設計思想を理解する
article【2025年3月版】Reactの状態管理「useState」「Redux Toolkit」「Jotai」「Zustand」を比較してみた
articleReact クリーンアーキテクチャ実践:UI・アプリ・ドメイン・データの責務分離
articleReact フック完全チートシート:useState から useTransition まで用途別早見表
articleReact 開発環境の作り方:Vite + TypeScript + ESLint + Prettier 完全セットアップ
articleReact とは? 2025 年版の特徴・強み・実務活用を一気に理解する完全解説
articleESLint を Yarn + TypeScript + React でゼロから構築:Flat Config 完全手順(macOS)
article【徹底比較】Preact vs React 2025:バンドル・FPS・メモリ・DX を総合評価
articleNext.js の Route Handlers で multipart/form-data が受け取れない問題の切り分け術
articleMCP サーバー で社内ナレッジ検索チャットを構築:権限制御・要約・根拠表示の実装パターン
articleRAG スタック比較:ベクタ DB(Pinecone/Weaviate/pgvector)× GPT-5 の相性と指標
articleLodash-es と lodash の違いを理解してプロジェクトに最適導入
articleFlutter の状態管理を設計する:Riverpod/Bloc/Provider/Redux の実務指針
articleLlamaIndex で社内ナレッジ QA ボット:権限別回答と出典表示で信頼性担保
blogiPhone 17シリーズの発表!全モデルiPhone 16から進化したポイントを見やすく整理
blogGoogleストアから訂正案内!Pixel 10ポイント有効期限「1年」表示は誤りだった
blog【2025年8月】Googleストア「ストアポイント」は1年表記はミス?2年ルールとの整合性を検証
blogGoogleストアの注文キャンセルはなぜ起きる?Pixel 10購入前に知るべき注意点
blogPixcel 10シリーズの発表!全モデル Pixcel 9 から進化したポイントを見やすく整理
blogフロントエンドエンジニアの成長戦略:コーチングで最速スキルアップする方法
review今の自分に満足していますか?『持たざる者の逆襲 まだ何者でもない君へ』溝口勇児
reviewついに語られた業界の裏側!『フジテレビの正体』堀江貴文が描くテレビ局の本当の姿
review愛する勇気を持てば人生が変わる!『幸せになる勇気』岸見一郎・古賀史健のアドラー実践編で真の幸福を手に入れる
review週末を変えれば年収も変わる!『世界の一流は「休日」に何をしているのか』越川慎司の一流週末メソッド
review新しい自分に会いに行こう!『自分の変え方』村岡大樹の認知科学コーチングで人生リセット
review科学革命から AI 時代へ!『サピエンス全史 下巻』ユヴァル・ノア・ハラリが予見する人類の未来