【React】Zustand入門 シンプルなグローバル状態管理

【React】Zustand入門 シンプルなグローバル状態管理

軽量な状態管理ライブラリ「Zustand」を導入し、Reactのグローバル状態管理を効率化する方法を解説します。

React #Zustand#React#状態管理

【React】Zustand入門 シンプルなグローバル状態管理

サムネイル

軽量な状態管理ライブラリ「Zustand」を導入し、Reactのグローバル状態管理を効率化する方法を解説します。

更新日: 7/15/2025

Zustandとは?

Zustandは、Reduxのような定型的なコードを必要とせず、Reactのフックに基づいたシンプルなAPIでグローバル状態を管理できるライブラリです。

  • ミニマルなAPI: create関数ひとつで、状態(state)とそれを更新するアクション(action)を持つ「Store」を定義できます。
  • Hookベース: コンポーネント内では、カスタムフックを呼び出すだけで状態にアクセスできます。
  • パフォーマンス: 必要なデータだけをセレクターで選択するため、関係のない状態更新による不要な再レンダリングを防ぎます。
  • Context Provider不要: アプリケーションのルートを<Provider>でラップする必要がありません。

インストール

npm install zustand
# or
yarn add zustand

基本的な使い方

1. Storeの作成

create関数を使い、状態とアクションを定義します。set関数が状態を更新する役割を担います。

// stores/useCounterStore.ts
import { create } from 'zustand';

interface CounterState {
  count: number;
  increment: () => void;
  decrement: () => void;
}

export const useCounterStore = create<CounterState>((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
  decrement: () => set((state) => ({ count: state.count - 1 })),
}));

2. コンポーネントでの利用

作成したStoreは、コンポーネント内でフックとして呼び出すだけで利用できます。

import { useCounterStore } from '@/stores/useCounterStore';

const CounterComponent = () => {
  const { count, increment, decrement } = useCounterStore();

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={increment}>+1</button>
      <button onClick={decrement}>-1</button>
    </div>
  );
};

3. セレクターによる最適化

Storeから必要な状態のみを抽出することで、不要な再レンダリングを防ぐことができます。

// count のみが変更されたときだけ再レンダリングされる
const count = useCounterStore((state) => state.count);

非同期アクション

async/awaitを使い、Store内で直接非同期処理を記述できます。

import { create } from 'zustand';

interface UserState {
  users: { id: number; name: string }[];
  fetchUsers: () => Promise<void>;
}

export const useUserStore = create<UserState>((set) => ({
  users: [],
  fetchUsers: async () => {
    try {
      const response = await fetch('/api/users');
      const users = await response.json();
      set({ users });
    } catch (error) {
      console.error('Failed to fetch users', error);
    }
  },
}));

ミドルウェアの活用

Zustandは、機能を拡張するためのミドルウェアがあります。

Immer

immerミドルウェアを使うと、ネストした複雑な状態を安全に更新できます。

import { create } from 'zustand';
import { immer } from 'zustand/middleware/immer';

// set((state) => { state.deep.nested.value = 'new'; }); のようにミューテーションが可能になる
export const useImmerStore = create<MyState>()(
  immer((set) => ({
    // ...
  }))
);

Persist

persistミドルウェアは、Storeの状態をlocalStorageなどのストレージに自動的に保存・復元します。

import { create } from 'zustand';
import { persist } from 'zustand/middleware';

export const useSettingsStore = create<SettingsState>()(
  persist(
    (set) => ({
      theme: 'light',
      setTheme: (theme) => set({ theme }),
    }),
    {
      name: 'settings-storage', // ストレージのキー名
    }
  )
);

DevTools

devtoolsミドルウェアを導入すると、Redux DevTools拡張機能で状態の変更履歴を追跡・デバッグできます。

import { create } from 'zustand';
import { devtools } from 'zustand/middleware';

export const useDevToolsStore = create<MyState>()(
  devtools(
    (set) => ({
      // ...
    }),
    {
      name: 'my-store', // DevToolsでの表示名
    }
  )
);

ミドルウェアは、devtools(persist(immer(...)))のように組み合わせて利用することも可能です。

Storeのテスト

ZustandのStoreはReactから独立しているため、@testing-library/reactrenderHookを使って簡単にテストできます。

import { renderHook, act } from '@testing-library/react';
import { useCounterStore } from '@/stores/useCounterStore';

describe('useCounterStore', () => {
  // 各テストの前に状態をリセット
  beforeEach(() => {
    useCounterStore.setState({ count: 0 });
  });

  it('should increment count', () => {
    const { result } = renderHook(() => useCounterStore());
    
    act(() => {
      result.current.increment();
    });
    
    expect(result.current.count).toBe(1);
  });
});

まとめ

Zustandは、簡単なだけでなく、ミドルウェアによる拡張性やパフォーマンスも十分で、プロジェクトの規模を問わずReactの状態管理の有力な選択肢かなとおもいます。

検索

検索条件に一致する記事が見つかりませんでした