Reactの状態管理2025:「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エコシステムの進化に合わせ、最適な状態管理戦略を選び続けることが、プロダクトの品質と開発体験を高める鍵となります。
本記事が、皆さまの状態管理選定と実装において、少しでも参考になれば幸いです。