Zustand vs Jotai — どちらを選ぶべきか?

テクノロジー

Zustand vs Jotai — どちらを選ぶべきか?

どちらも軽量でシンプルなReact状態管理ライブラリとして人気を集めているZustandとJotai。設計思想の違いを理解して、プロジェクトに合った選択をしよう。


概要

Zustand

作者: pmndrs(Poimandres)
バンドルサイズ: 約1KB
設計思想: 集約型(Store型)

単一のストアにstateとactionをまとめる設計。Redux的な発想をシンプルに再解釈したライブラリ。ボイラープレートが少なく、学習コストが低いのが特徴。

Jotai

作者: pmndrs(Poimandres)
バンドルサイズ: 約3KB
設計思想: 分散型(Atom型)

状態をatomという単位で細かく管理するボトムアップ設計。RecoilにインスパイアされつつAPIをよりシンプルに実現している。


コード比較

カウンターの例

Zustand — store定義 + 使用

import { create } from 'zustand'

const useStore = create(set => ({
  count: 0,
  inc: () => set(s => ({ count: s.count + 1 })),
}))

// コンポーネント内
const { count, inc } = useStore()

Jotai — atom定義 + 使用

import { atom, useAtom } from 'jotai'

const countAtom = atom(0)

// コンポーネント内
const [count, setCount] = useAtom(countAtom)
const inc = () => setCount(c => c + 1)

非同期fetchの例

Zustand — 非同期アクション

const useStore = create(set => ({
  user: null,
  loading: false,
  fetchUser: async (id) => {
    set({ loading: true })
    const res = await fetch(`/api/users/${id}`)
    const user = await res.json()
    set({ user, loading: false })
  },
}))

Jotai — 非同期atom(Suspense連携)

const userIdAtom = atom(1)

const userAtom = atom(async (get) => {
  const id = get(userIdAtom)
  const res = await fetch(`/api/users/${id}`)
  return res.json()
})

// Suspenseで自動ローディング管理

派生stateの例

Zustand — selectorで派生値を計算

const useStore = create(set => ({
  items: [],
  // ...actions
}))

const total = useStore(
  s => s.items.reduce((acc, i) => acc + i.price, 0)
)

Jotai — 派生atomを宣言的に定義

const itemsAtom = atom([])

const totalAtom = atom(
  (get) => get(itemsAtom).reduce((acc, i) => acc + i.price, 0)
)

const total = useAtomValue(totalAtom)

詳細比較

観点

Zustand

Jotai

設計思想

単一ストアに集約。Fluxアーキテクチャに近い

atomを分散配置。ボトムアップ設計

学習コスト

非常に低い。useStore一つで始められる

低いが、派生・非同期atomに慣れるまで少し時間がかかる

再レンダリング

selectorで最適化可能(手動指定が必要)

atomが変化したコンポーネントのみ自動で最適化

TypeScript

型推論が効きやすい。create<T>()で明示も可能

atom<T>()で型付け。派生atomも型が自動で伝播

Suspense対応

公式サポートなし(ミドルウェアで対応可)

非同期atomがそのままSuspenseと統合できる

DevTools

Redux DevTools対応。デバッグしやすい

Jotai DevToolsが提供されているが発展途上

ミドルウェア

persist・immer・subscribeWithSelectorなど豊富

jotai/utilsでatomWithStorageなど一通り揃っている

React外での使用

getState/setStateで直接操作可能

基本的にReact(またはProvider)が必要


こんな場合に使い分けよう

Zustand が向いているケース

  • グローバルなUIステート(モーダル、トースト、サイドバー)の管理
  • 認証情報やユーザー設定の一元管理
  • ReduxやVuexから移行したい
  • サーバーサイドや非ReactコードからもStateを操作したい
  • チームが大きく、ストアを一元管理したい

Jotai が向いているケース

  • 細かい粒度の状態が多く、分散して管理したい
  • 非同期データフェッチをSuspenseで宣言的に扱いたい
  • 状態間の依存関係が複雑(派生atomが便利)
  • コンポーネントごとに独立したStateを持ちたい
  • テスト時にatomを差し替えてモックしたい

まとめ

Zustand を選ぶ場合
ストア設計の経験がある、グローバルStateをシンプルに管理したい、React外との連携がある、チーム開発でデバッグ体験を重視したい場合に向いている。

Jotai を選ぶ場合
Reactらしいボトムアップ思考が好き、Suspenseと組み合わせて非同期処理を宣言的に書きたい、細かい状態の再レンダリング最適化を自動化したい場合に向いている。

どちらも同じpmndrsチームが開発しており、品質・メンテナンスともに信頼できる選択肢だ。設計思想の違いを理解した上で、プロジェクトの規模や要件に合わせて選ぼう。