Reduxのボイラープレートにさよなら:ZustandでReactのステート管理を「超軽量」に

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Zustand:Reduxという重石を捨てて軽やかに走る

Reduxでユーザー名を一つ更新するためだけに、何十行ものボイラープレートを書いて「疲れ果てた」経験はありませんか?ESLintやPrettierでコードを整えるのと同じように、ステート管理もシンプルに保つことが重要です。もしそうなら、Zustandが救世主になります。ミニマリズムを追求したこのライブラリは、サイズがわずか1.1KB。アプリをProviderでラップする必要も、actions.jsreducers.jsといった複雑な構成も不要です。

一瞬でインストール完了:

npm install zustand
# または
yarn add zustand

試しにカウンター管理用のストアを作成してみましょう。すぐにその違いを実感できるはずです:

import { create } from 'zustand'

const useCounterStore = create((set) => ({
  count: 0,
  inc: () => set((state) => ({ count: state.count + 1 })),
  dec: () => set((state) => ({ count: state.count - 1 })),
}))

function Counter() {
  const { count, inc, dec } = useCounterStore()
  return (
    <div>
      <h1>{count}</h1>
      <button onClick={inc}>増やす</button>
      <button onClick={dec}>減らす</button>
    </div>
  )
}

最大のメリットは?フックをインポートしてそのまま使うだけ。ルートファイルで面倒な<Provider>の設定をする必要はありません。

実務プロジェクトでZustandを選ぶべき理由

以前参加した複雑なダッシュボードのプロジェクトでは、Context APIの使用がパフォーマンスのボトルネックになっていました。ユーザーが入力欄に一文字打つたびに、階層下にある大量のチャートが無駄に再レンダリングされていたのです。Zustandに切り替えたところ、スマートなレンダリング機構のおかげでUIのレスポンス速度が約30%向上しました。

Zustandは3つの大きな問題を根本的に解決します:

  • レンダリングの制御: 実際にデータが変更されたコンポーネントのみが再レンダリングされます。
  • API hiện đại: React Hooksのパワーを最大限に活用しており、開発者に非常に親切です。
  • 柔軟なアクセス: フックを使わずに、API設定ファイルやユーティリティ関数内から直接ステートを取得することも可能です。

実戦における主要機能

1. 非同期アクション(Async Actions)の処理が驚くほど簡単

Redux-Thunkのようなミドルウェアのことは忘れてください。Zustandなら、通常のJavaScript関数のようにストア内でasync/awaitを直接使えます。効率的なRESTful APIを構築したバックエンドからデータを取得する際も、コードを非常にシンプルに保てます。

const useUserStore = create((set) => ({
  users: [],
  loading: false,
  fetchUsers: async () => {
    set({ loading: true })
    try {
      const res = await fetch('https://api.example.com/users')
      const data = await res.json()
      set({ users: data, loading: false })
    } catch (error) {
      set({ loading: false })
      console.error("データ読み込みエラー:", error)
    }
  },
}))

2. Persistミドルウェア – ステートの自動保存

カート情報やテーマ設定をlocalStorageに保存する機能は、通常多くのロジックを必要としますが、Zustandならラップ関数を一つ使うだけで解決します。

import { persist } from 'zustand/middleware'

const useSettingsStore = create(
  persist(
    (set) => ({
      theme: 'dark',
      toggleTheme: () => set((s) => ({ theme: s.theme === 'dark' ? 'light' : 'dark' })),
    }),
    { name: 'user-preferences' }
  )
)

パフォーマンス最適化:ストアを「丸ごと」取り出さない

初心者がよくやってしまうミスに、const state = useStore()のようにストア全体を取得することがあります。これではストア内のどれか一つが変わるたびにコンポーネントが再レンダリングされてしまいます。最適化のためにSelectorを使用しましょう:

// countが変更された時のみ再レンダリング
const count = useCounterStore((state) => state.count)
const inc = useCounterStore((state) => state.inc)

このアプローチにより、ストアが肥大化してもアプリケーションの動作を滑らかに保つことができます。

プロジェクト規模拡大に伴うストア構成のベストプラクティス

Slicesパターンの適用

すべてのロジックを一つのファイルに詰め込まないでください。ストアを「Slice」と呼ばれる単位(例:AuthSlice、CartSlice)に分割し、それらを統合します。この方法により、コードの可読性と保守性が向上し、効果的なコードレビューの際もロジックの理解がスムーズになります。

Redux DevToolsの活用

ZustandはRedux DevToolsと非常に相性が良いです。Reduxを使っている時と同じようにデバッグやステートの変更履歴の確認ができますが、開発者向け無料オンラインツールなどと併せて活用することで、作業効率はさらに向上します。

ストアを使わない時を見極める

使いすぎには注意しましょう。特定のコンポーネント内だけで完結する状態(例:単一のモーダルの開閉状態など)であれば、React標準のuseStateが最適です。複数の画面で共有する必要があるデータや、永続化が必要なデータのみをストアに配置しましょう。

終わりに

Zustandは単なるライブラリではなく、実用的かつモダンなステート管理の考え方そのものです。冗長な規約よりも、開発スピードと実際のパフォーマンスを優先します。新しいReactプロジェクトや、Next.jsでの開発を始めるなら、ぜひZustandを試してみてください。きっと後悔しないはずです。

ハッピーコーディング!

Share: