2026年の推奨スタックは「Zustand(クライアント)+ TanStack Query(サーバー)+ MMKV(永続化)」の3層構成で、合計約20KBに収まる。
Zustandは選択的サブスクリプションによる再レンダー最小化とTypeScript適性で、React Native環境でも第一候補になる。
Jotaiはatomごとに購読を分離するため、細粒度な派生値が多いフォームや設定画面で最も効率がよい。
Redux Toolkitは監査ログや複雑な非同期フローが要件になるエンタープライズ用途では今も現役で、RTK Queryとの統合が強み。
Legend-State v3はfine-grained reactivityとMMKV連携で、オフラインファーストのモバイルアプリで最高クラスの性能を出す。
New Architectureとの互換性はuseSyncExternalStoreベースのライブラリ(Zustand・Jotai・Legend-State)が高く、レガシーな深いContextツリーは避けるべき。
目次
React Nativeの状態管理は2026年にどう変わったか
主要4ライブラリの徹底比較
ZustandをReact Nativeで使う実装パターン
Jotaiのアトミック設計とReact Nativeでの効き所
Redux Toolkitはもう古いのか
Legend-State v3とオフラインファースト設計
サーバー状態とクライアント状態を分ける
プロジェクト規模別の選び方フロー
New Architectureとの互換性で注意すべき点
React Nativeの状態管理は2026年にどう変わったか
状況を数字で押さえておくと選択に迷いません。State of React 2025調査(2026年2月公開)では、Zustandの利用率が2023年の28%から2025年の50%へ倍増し、Redux Toolkitは54%で横ばい、Jotaiは13%から19%へ緩やかに成長しました。npm週間ダウンロードもZustandは7,290万を突破しており、Reduxの成長率を上回っています。この数字が意味するのは「デフォルトの選択肢が変わった」ということです。新規プロジェクトで最初に手を伸ばすべきはもはやReduxではなく、Zustandです。
Webからモバイルに来た開発者にとって朗報なのは、これらのライブラリがReact Native側でも同じAPIで動く 点です。react-reduxのプロバイダ、zustandのフック、jotaiのatomは、いずれもWeb向けの実装がそのまま動きます。差分が出るのはむしろ周辺で、たとえば永続化はWebならlocalStorageですが、React Nativeでは@react-native-async-storage/async-storageやreact-native-mmkvを明示的に噛ませる必要があります。特にMMKVはネイティブ側でJSI経由の同期APIを提供するため、React Native環境における永続化のデファクトになっています。
もうひとつ2026年で重要なのは、New Architecture がRN 0.76からデフォルトになったことです。Fabric・TurboModulesを前提にした環境では、React 18が導入したuseSyncExternalStoreにきちんと乗っているストアライブラリのほうが同時実行機能との相性がよく、Concurrent Renderingでtearing(状態の不整合)が起きにくくなります。
主要4ライブラリの徹底比較
まず表で全体像を掴んでください。ベンチマーク値はM1 MacBook Proで1,000コンポーネントが購読する状況を計測したものです。
比較軸 Zustand Jotai Redux Toolkit Legend-State v3
バンドルサイズ(min+gzip) 約3KB 約4KB 約15KB(react-redux含む) 約4KB
単一更新の描画時間 12ms 14ms 18ms 約8ms
初期パース(4倍CPUスロー) 8ms 9ms 34ms 約6ms
学習コスト 低(フック1つ) 低〜中(atom概念) 中(slice/dispatch/selector) 中(observable設計)
Provider必須か 不要 推奨(スコープ用) 必須 不要
永続化プラグイン あり(MMKV/AsyncStorage対応) あり(jotai/utils) あり(redux-persist) 公式・MMKV最速
DevToolsサポート Redux DevTools対応 限定的 ネイティブ対応(最強) Legend Kit経由
向いている規模 小〜大まで幅広い 細粒度が多い中規模 大規模・監査要件あり オフラインファースト、リスト重視
数字の意味を実装に落とすと、Zustandは「selectorを渡した瞬間、その値だけを購読する」ため、100件のTODOのうち1件だけ変わっても他の99件は再レンダーされません。Redux ToolkitでもuseSelectorで同じことができますが、参照等価性の罠にはまりやすく、明示的にcreateSelectorでメモ化する必要があります。この違いが「デフォルトの安全性」というかたちで日常のパフォーマンスに効いてきます。
注意: ここで挙げた数字はあくまで参考値で、実アプリのボトルネックは大抵、状態ライブラリの選択ではなくリストの仮想化や画像のデコードにあります。プロファイリングの手順はFlatList・FlashList・Legend Listの徹底比較記事 で詳しく解説しています。
ZustandをReact Nativeで使う実装パターン
Zustandの魅力は「Providerが要らない」「フック1つで購読できる」「TypeScript推論が素直」の3点です。Web版と同じくストアを定義してフックとして呼び出すだけですが、React Native環境では永続化をMMKVに差し替えるのが定番です。以下は認証状態を管理する典型的なストアです。
// stores/authStore.ts
import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import { MMKV } from 'react-native-mmkv';
const storage = new MMKV({ id: 'auth' });
const mmkvStorage = {
getItem: (name: string) => storage.getString(name) ?? null,
setItem: (name: string, value: string) => storage.set(name, value),
removeItem: (name: string) => storage.delete(name),
};
type AuthState = {
token: string | null;
userId: string | null;
signIn: (token: string, userId: string) => void;
signOut: () => void;
};
export const useAuthStore = create<AuthState>()(
persist(
(set) => ({
token: null,
userId: null,
signIn: (token, userId) => set({ token, userId }),
signOut: () => set({ token: null, userId: null }),
}),
{
name: 'auth-storage',
storage: createJSONStorage(() => mmkvStorage),
}
)
);
使う側は普通のフックです。ここでポイントなのはselectorを必ず渡す ことです。渡さないとストア全体を購読することになり、無関係な変更でも再レンダーが走ります。
// screens/HomeScreen.tsx
import { useAuthStore } from '../stores/authStore';
export function HomeScreen() {
// OK: tokenだけを購読
const token = useAuthStore((s) => s.token);
// NG: ストア全体を購読してしまう
// const state = useAuthStore();
const signOut = useAuthStore((s) => s.signOut);
return token ? <LoggedIn onSignOut={signOut} /> : <SignInScreen />;
}
Webから来た開発者が引っかかりやすいのは、ZustandはRedux DevToolsにそのまま接続できる 点です。devtoolsミドルウェアを噛ませればアクション履歴を可視化でき、「Reduxで慣れた開発体験を軽量に再現」できます。監査ログのような重い要件でなければ、DevTools目当てでReduxを選ぶ必要はもはやありません。
Jotaiのアトミック設計とReact Nativeでの効き所
Jotaiは公式ドキュメント にある通り、Recoilにインスパイアされたatom(原子)ベースのAPIです。状態を大きな1つのオブジェクトではなく、小さな独立した単位で持たせるという発想です。これがReact Nativeで刺さる場面は、フォームや設定画面のように、多数の独立した値が並列に存在する画面 です。1つのフィールドだけを更新したときに他のフィールドは再レンダーせずに済むため、細粒度のパフォーマンスが自然と手に入ります。
// atoms/settingsAtoms.ts
import { atom } from 'jotai';
export const notificationsAtom = atom(true);
export const darkModeAtom = atom(false);
export const languageAtom = atom<'ja' | 'en'>('ja');
// 派生atom:現在の言語に応じた挨拶
export const greetingAtom = atom((get) => {
return get(languageAtom) === 'ja' ? 'こんにちは' : 'Hello';
});
// screens/SettingsScreen.tsx
import { useAtom, useAtomValue } from 'jotai';
import { Switch, Text, View } from 'react-native';
import { notificationsAtom, darkModeAtom, greetingAtom } from '../atoms/settingsAtoms';
export function SettingsScreen() {
const [notifications, setNotifications] = useAtom(notificationsAtom);
const [dark, setDark] = useAtom(darkModeAtom);
const greeting = useAtomValue(greetingAtom);
// notificationsを切り替えてもdarkのSwitchは再レンダーされない
return (
<View>
<Text>{greeting}</Text>
<Switch value={notifications} onValueChange={setNotifications} />
<Switch value={dark} onValueChange={setDark} />
</View>
);
}
React NativeでのJotai導入で覚えておきたいのは、Providerを敢えて使うとテストやモーダル画面のスコープ分離ができる点です。たとえば「新規作成モーダル内だけで一時的なフォーム状態を管理し、閉じたら破棄」という設計は、Providerでスコープを切ってatomのライフサイクルを局所化することで綺麗に書けます。ReduxのcombineReducersとは違い、宣言的に組み合わせられるのがWebから来た自分の目にはとても新鮮でした。
結論から書くと、Redux Toolkitは古くはないが、デフォルト選択肢ではなくなった というのが正確です。以下の要件が1つでも当てはまるなら、いまでもRTKが最良解になり得ます。
10人以上のエンジニアが並行して同じ状態を触る規模のチーム
金融・医療・ゲームなど、状態遷移の監査ログが規制要件で必要
ミドルウェアで複雑な非同期パイプラインを構成する必要がある(redux-saga、redux-observable、RTK Queryのキャッシュ制御)
時系列でのステート再生(time-travel debugging)を運用に組み込みたい
RTK自体は昔の「Reduxはボイラープレートが多い」批判にきちんと応えていて、createSliceで1つのファイルに集約できます。React Native + New Architecture環境でも問題なく動きますが、注意点はバンドルサイズです。RTK Query まで載せると15KBを超え、初期パースがZustandの約4倍遅くなります。低スペックAndroid端末をターゲットにする場合はTanStack Queryと素のZustandで代替したほうが起動体感が明確に良くなります。
// features/todos/todosSlice.ts (Redux Toolkit)
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
type Todo = { id: string; title: string; done: boolean };
const todosSlice = createSlice({
name: 'todos',
initialState: [] as Todo[],
reducers: {
added: (state, action: PayloadAction<Todo>) => {
state.push(action.payload); // Immerで直接mutation可
},
toggled: (state, action: PayloadAction<string>) => {
const t = state.find((x) => x.id === action.payload);
if (t) t.done = !t.done;
},
},
});
export const { added, toggled } = todosSlice.actions;
export default todosSlice.reducer;
Legend-State v3とオフラインファースト設計
Legend-Stateは2026年のReact Nativeで最も勢いのある新顔です。公式サイト によれば、fine-grained reactivity(細粒度の反応性)とMMKV連携により、ベンチマークによっては純粋なvanilla JavaScriptを上回る 速度を出します。特にリストの並び替えや全行置換のようなケースで顕著で、Legend Listと組み合わせるとFlashListより低いCPU使用率とメモリ使用量が観測されています。
Legend-Stateの核はobservableで、状態を関数呼び出しで取り出し・更新します。以下は同じTODOリストをLegend-Stateで書いた例です。
// stores/todos$.ts
import { observable } from '@legendapp/state';
import { syncObservable } from '@legendapp/state/sync';
import { ObservablePersistMMKV } from '@legendapp/state/persist-plugins/mmkv';
type Todo = { id: string; title: string; done: boolean };
export const todos$ = observable<Todo[]>([]);
// MMKVに自動永続化。オフライン変更も内部で追跡される
syncObservable(todos$, {
persist: { name: 'todos', plugin: ObservablePersistMMKV },
});
// screens/TodosScreen.tsx
import { observer } from '@legendapp/state/react';
import { todos$ } from '../stores/todos$';
export const TodosScreen = observer(function TodosScreen() {
// .get() を呼んだコンポーネントだけが購読対象になる
const items = todos$.get();
return (
<LegendList
data={items}
keyExtractor={(t) => t.id}
renderItem={({ item }) => (
<TodoRow
title={item.title}
done={item.done}
onToggle={() => todos$[items.indexOf(item)].done.set((d) => !d)}
/>
)}
/>
);
});
Legend-Stateの本当の強みは、オフラインで加えた変更を自動で追跡し、オンライン復帰時に差分だけをサーバーへ同期 する仕組みが組み込みで用意されていることです。競合解決までライブラリ側で扱えるため、モバイル開発では自前で書きたくない層をまるごと引き受けてくれます。正直、前回のフィールド作業向けアプリでこの同期層を自前実装しようとして地獄を見たので、その経験からすると価値は大きい。書き味に癖はあるものの、旅行アプリやフィールド作業用のアプリなど「電波が不安定でも動くこと」が要件になるドメインでは、他ライブラリでは書けない領域まで届きます。
サーバー状態とクライアント状態を分ける
2026年のベストプラクティスで最も重要な概念は「サーバー状態はサーバー状態専用のライブラリで扱う」ことです。ここを分けないままZustandやReduxにfetch結果を丸ごと入れると、キャッシュ・再検証・楽観的更新・リトライを全部自前で書く羽目になり、コードの半分がその手のグルーコードになります。TanStack Query を挟むだけで、これらの関心事はライブラリ側に押し込められます。
// hooks/useArticles.ts
import { useQuery } from '@tanstack/react-query';
async function fetchArticles() {
const r = await fetch('https://api.example.com/articles');
if (!r.ok) throw new Error('Failed');
return r.json() as Promise<Article[]>;
}
export function useArticles() {
return useQuery({
queryKey: ['articles'],
queryFn: fetchArticles,
staleTime: 60_000, // 60秒はキャッシュを新鮮とみなす
gcTime: 5 * 60_000, // 5分後にキャッシュ破棄
});
}
ZustandにはcurrentUserのような「サーバーから取得したが以降はローカルで更新する軽い値」だけを置き、記事一覧やコメントのようにサーバーが正しさの源泉になる値はTanStack Queryに任せる。この分担が2026年の標準です。フォームはReact Hook Form とZodの組み合わせに任せ、URL/ディープリンクのパラメータはナビゲーションライブラリの責務にする — 詳しくはReact Navigation 8完全ガイド でも触れていますが、状態を「持ち場所」で仕分けすると、どのライブラリを使うか悩む場面が激減します。
Tip: TanStack Queryのキャッシュ内容をZustandからも参照したい場合は、queryClient.getQueryData()を薄いラッパー関数で包み、Zustandのアクションから呼び出すだけで十分です。二重管理は絶対に避けてください。
プロジェクト規模別の選び方フロー
実プロジェクトで悩んだときの目安を、規模別にまとめました。
個人開発・PoC・小規模アプリ(〜画面20枚)
Zustand + TanStack Query + MMKVで十分です。総バンドルは20KB程度、Providerの階層地獄もなく、テストも書きやすい。最初の1週間で状態管理の学習コストを回収できます。
中規模SaaS・BtoBアプリ(画面20〜80枚)
ドメインが横に広がるならZustand + TanStack Queryを基本に、細粒度が必要な設定画面や複雑なフォームだけJotaiを部分導入。Reduxのstoreを1つ持って全部そこに詰める旧来の設計より、ライブラリを混在させるほうが結果的にコードが読みやすくなります。
大規模エンタープライズ・監査要件あり(画面80枚以上、開発者10人以上)
Redux Toolkit + RTK Queryを軸に、UIローカルはuseState、重い派生値はメモ化selectorという古典的構成が結局最も破綻しません。State遷移のロギング、time-travel debugging、規約による統制の恩恵が大きい規模帯です。
オフラインファースト・現場アプリ・エディタ系
Legend-State v3が最有力候補。MMKV永続化と自動差分同期の組み合わせは、自前実装だと数千行になる領域を、ライブラリ選択1つで肩代わりしてくれます。
New Architectureとの互換性で注意すべき点
React Native 0.76以降New Architectureがデフォルトになったことで、状態管理の選び方にも影響が出ています。Concurrent Renderingが有効な環境では、外部ストアからの購読はuseSyncExternalStoreフックを介する必要があり、これに乗っていないライブラリではtearing(同一レンダー内で異なるバージョンの状態が見える現象)が発生し得ます。Zustand・Jotai・Legend-State・Redux(react-redux 8以降)はいずれも対応済みなので、この点は現時点で心配ないでしょう。ちなみにアニメーションと状態を絡めるときは、Reanimated 4の完全ガイド で解説しているsharedValueとJSステートの使い分けが重要になります。
もうひとつの落とし穴は、TurboModulesを使うネイティブモジュールから状態を非同期に更新するケースです。JSI経由でメインJSスレッド外から更新を投げると、Reactのバッチングをすり抜けて連続的な再レンダーが走ることがあります。ZustandならuseShallowで、Jotaiなら派生atomで購読側を絞る、あるいはLegend-Stateならobservableに集約してからバッチ化する — このいずれかの手当が必要です。
警告: React 19 で提案されているsignalsやuse APIは、まだReact Native側の実装が追いついていない領域があります。本番アプリで先取りする場合は、New Architecture有効・Hermes有効・Reanimated 4以降の組み合わせでE2Eテストを厚めに走らせてから採用してください。
よくある質問
React NativeでZustandとReduxのどちらを選ぶべきですか?
新規プロジェクトならZustandが第一候補です。Reduxを選ぶ理由は「監査ログ、time-travel debugging、10人以上のチーム規約が必要」の3つに絞られてきており、そのどれも該当しなければZustandのほうが起動速度・コード量・学習コストすべてで有利です。
Zustandは大規模アプリでも本当に耐えられますか?
ストアを機能ごとに分割し、selectorで購読範囲を絞れば大規模でも耐えます。破綻するのは1つの巨大ストアに全部詰めるアンチパターンで、これはRedux時代から変わりません。ドメインごとにuseAuthStore / useCartStoreのように分けるのが定石です。
JotaiとRecoilの違いは何ですか?
JotaiはRecoilにインスパイアされたAPIですが、Recoilはメンテナンスが実質停止しており、Jotaiが後継の位置にいます。Jotaiのほうがバンドルが小さく、Provider不要でも動作するため、React Nativeでは今からRecoilを選ぶ理由はほぼありません。
Legend-Stateは本番環境で使えますか?
v3に到達しており、Legend List v3と併用した本番アプリも増えています。オフラインファーストが要件のドメインでは、他ライブラリで書くと数千行かかる領域が数十行で済むため、むしろ本番向きです。ただしAPI設計が独特なため、チーム全員が最初に半日ドキュメントに向き合う覚悟は必要です。
サーバーの取得結果はZustandに入れるべきですか?
入れないでください。TanStack QueryかRTK Queryに任せるのが2026年の標準です。Zustandには「ログイン中ユーザーの表示名」のような、以降ローカル更新される軽い派生値だけを置くのが健全な線引きです。