1. ホーム
  2. 記事一覧
  3. 【初心者向け】ZustandでReactの状態管理を簡単に!使い方を徹底解説!

2025.01.31

【初心者向け】ZustandでReactの状態管理を簡単に!使い方を徹底解説!

    はじめに

    Reactアプリの開発では欠かせない「状態管理」。有名なツールにReduxがありますが、「もっとシンプルに使いたい」「初心者でも学びやすいものを探している」という方におすすめなのが Zustandです。

    Zustandは、React向けの状態管理ライブラリで、フックを活用したシンプルなAPIが特徴です。その使いやすさから、多くの開発者に利用されている人気のライブラリです。

    この記事では、Zustandの基本的な使い方をハンズオン形式で解説します。初心者でも記事を読み終えるころには、Zustandを活用してReactアプリの状態管理ができるようになることを目指します。シンプルかつ効率的な状態管理の方法を一緒に学びましょう。

    Zustandとは?

    Zustandは、Reactアプリ向けの軽量でシンプルな状態管理ライブラリです。そのAPIはReactフックにもとづいており、直感的に扱える設計が特徴です。初心者でも簡単に導入できるため、初めての状態管理ライブラリとしても適しています。

    Zustandという名前はドイツ語で「状態」を意味します。発音については、ドイツ語風に「ツーシュタント」と読まれることもあれば、英語圏では「ズースタンド」と呼ばれることが多いです。その正確な発音は地域や発信者によって異なる場合があります。

    Zustandが注目されている理由

    Zustand は、2024年の JavaScript Rising Stars ランキングで1位に輝きました。このランキングは、1年間で増えたGitHubスターの数をもとに注目のJavaScriptライブラリを発表するものです。この結果から、Zustandが現在最も注目されている状態管理ライブラリであることがわかります。

    出典:JavaScript Rising Stars

    https://risingstars.js.org/2024/en

    さらに、npm trends によると、Zustandのダウンロード数は徐々に増加しています。Reduxと比べるとまだ少ないものの、成長傾向にあります。また、開発は現在も活発に行われており、定期的なアップデートが提供されている点も安心です。

    出典:npm trends

    https://npmtrends.com/jotai-vs-recoil-vs-redux-vs-xstate-vs-zustand

    状態管理ライブラリとは

    まず、状態管理ライブラリについて簡単に触れておきましょう。これは、アプリ内で使用されるデータ(状態)を効率的かつ一元的に管理するためのツールです。

    状態とはアプリの「現在の状況」を表すもの

    「状態(state)」とは、アプリが「今どんな状況にあるか」を示すデータのことです。例えば、以下のようなデータが状態に該当します。

    • ユーザーのログイン状態
    • ショッピングカートに入っている商品
    • フォームに入力されたテキスト

    これらの状態は、ユーザーの操作によって頻繁に変化します。適切に管理することで、アプリの動作がスムーズになり、ユーザー体験を向上させることができます。

    状態管理ライブラリが必要な理由

    Reactには useState や useReducer といった状態管理の仕組みが用意されています。しかし、アプリが大規模になるにつれて、以下のような問題が発生することがあります。

    • 状態の複雑化

      アプリが扱う状態が増えると、「どの状態がどこで管理されているのか」が分かりにくくなり、開発や保守が難しくなります。

    • 状態の共有の複雑さ

      親コンポーネントから子コンポーネントへ状態を渡す際に、「バケツリレー」のようなコードが増え、全体の可読性が低下します。

    • パフォーマンスの問題

      状態の変更による不要な再レンダリングが発生し、アプリの動作が遅くなる場合があります。

    このような課題を解決するために、状態管理ライブラリが役立ちます。状態を一元管理し、コンポーネント間で簡単に共有できるようにすることで、コードの可読性や保守性を向上させることができます。

    Zustandの使い方

    ここでは、Reactアプリでカウントを増減できるシンプルな機能を例に、Zustandの使い方を解説します。まずは、Reactプロジェクトを構築した後のZustandをインストールする手順から始めましょう。

    インストール方法

    npmまたはyarnを使用してZustandをインストールします。

    # npmの場合
    npm install zustand
    
    # yarnの場合
    yarn add zustand

    storeの作成

    Zustandを使うには、まず状態管理用の store を作成します。create 関数を使用して簡単に作成できます。

    シンプルなstoreの例

    以下は、カウント数を管理するシンプルなstoreの例です。

    // Zustandのcreate関数をインポート
    import { create } from 'zustand'
    
    // storeの作成
    const useCounterStore = create((set) => ({
      count: 0, // 初期値
      increase: () => set((state) => ({ count: state.count + 1 })), // カウントを1増やす
      decrease: () => set((state) => ({ count: state.count - 1 })), // カウントを1減らす
    }));

    この create 関数を使用することで、状態(state)と、その状態を更新するための set 関数を定義できます。

    storeを利用する方法

    作成したstoreから状態や関数を取得して、Reactコンポーネント内で使用します。

    全ての状態を取得

    以下の記述はstore内の状態をすべて取得することができます。

    const state = useCounterStore(); // 全ての状態を取得

    しかし、この方法は状態が変更されるたびにコンポーネント全体が再レンダリングされるため、パフォーマンスに影響が出る場合があります。

    必要な状態や関数のみを取得

    不要な再レンダリングを防ぐには、以下のように必要な部分だけを個別に取得します。

    const count = useCounterStore((state) => state.count); // 状態を取得
    const increase = useCounterStore((state) => state.increase); // 関数を取得
    const decrease = useCounterStore((state) => state.decrease); // 関数を取得

    実際の使用例

    以下は、Zustandを使ってカウントを増減するシンプルなReactアプリの例です。

    import { create } from 'zustand';
    
    // ストアの作成
    const useCounterStore = create((set) => ({
      count: 0, // 初期値
      increase: () => set((state) => ({ count: state.count + 1 })), // カウントを1増やす
      decrease: () => set((state) => ({ count: state.count - 1 })), // カウントを1減らす
    }));
    
    function App() {
      const count = useCounterStore((state) => state.count); // 現在のカウントを取得
      const increase = useCounterStore((state) => state.increase); // カウント増加関数を取得
      const decrease = useCounterStore((state) => state.decrease); // カウント減少関数を取得
    
      return (
        <div>
          <h1>カウント: {count}</h1>
          <button onClick={increase}>+1</button>
          <button onClick={decrease}>-1</button>
        </div>
      );
    }
    
    export default App;

    set関数と状態の更新

    Zustandのset関数は、状態を更新するために使用します。ReactのuseStateと同様に、状態を直接変更せず、新しいオブジェクトを作成して更新します。 コード例の以下の部分は、state は現在の状態を参照し、カウントを 1 増やした新しい状態を返します。

    increase: () => set((state) => ({ count: state.count + 1 })), // カウントを1増やす

    ここまでがZustandを使った状態の定義や基本的な使い方の解説です。シンプルなAPIのおかげで、状態管理を簡単に導入できることをご覧いただけたと思います。次の章では、ハンズオン形式で実践的な例を通じて、Zustandをさらに深掘りしていきます。

    Zustandの使い方をハンズオンで学ぶ

    これまでに学んだZustandの基本を活かして、ここからはハンズオン形式で実際にコードを実装しながら、Zustandの理解を深めていきましょう。

    このハンズオンではReactの開発環境を使用します。環境構築についてはこの記事では解説しませんので、以下の記事を参考にしてください。

    【超入門】Reactの基礎を30分で学べる!インストールから基本コンポーネントまで

    https://envader.plus/article/341

    ハンズオンの概要

    このハンズオンでは、以下を学びます。

    • グローバルな状態管理

      storeを別ファイルに分け、複数コンポーネントで状態を共有します。

    • 複数の状態を管理

      数値(カウント)と文字列(テキスト)の状態を同時に管理します。

    • ミドルウェアの使用

      状態をlocalStorageに保存する「persist」ミドルウェアの使い方を紹介します。

    ファイル構成

    以下のように4つのファイルを作成し、役割ごとにコードを分けてアプリを構築します。

    src/
    ├── stores/
    │   └── useStore.js      // Zustand のstoreの定義
    ├── components/
    │   ├── Counter.jsx      // カウンターコンポーネント
    │   ├── TextInput.jsx    // テキスト入力コンポーネント
    │   └── App.jsx          // メインコンポーネント

    1. Zustand Storeの作成

    Zustand を利用した状態管理のロジックを定義します。ここでは、counttext の状態を保持し、状態を変更する set 関数を定義しています。

    // src/stores/useStore.js
    
    import { create } from 'zustand';
    
    const useStore = create((set) => ({
      count: 0,
      text: '',
      increase: () => set((state) => ({ count: state.count + 1 })),
      decrease: () => set((state) => ({ count: state.count - 1 })),
      setText: (newText) => set({ text: newText }),
    }));
    
    export default useStore;

    2. カウンターコンポーネントの作成

    カウンターを表示し、increasedecrease の操作を提供するUIコンポーネントです。

    // src/components/Counter.js
    
    import useStore from '../stores/useStore';
    
    function Counter() {
      const count = useStore((state) => state.count);
      const increase = useStore((state) => state.increase);
      const decrease = useStore((state) => state.decrease);
    
      return (
        <div>
          <h1>カウント: {count}</h1>
          <button onClick={increase}>+1</button>
          <button onClick={decrease}>-1</button>
        </div>
      );
    }
    
    export default Counter;

    3. テキスト入力コンポーネント

    入力されたテキストを管理し、入力内容を表示するUIコンポーネントです。

    // src/components/TextInput.jsx
    
    import useStore from '../stores/useStore';
    
    function TextInput() {
      const text = useStore((state) => state.text);
      const setText = useStore((state) => state.setText);
    
      return (
        <div>
          <input
            type="text"
            value={text}
            onChange={(e) => setText(e.target.value)}
          />
          <p>入力したテキスト: {text}</p>
        </div>
      );
    }
    
    export default TextInput;

    4.メインコンポーネントの作成

    CounterTextInput を統合して表示するメインコンポーネントです。

    // src/components/App.jsx
    
    import Counter from './Counter';
    import TextInput from './TextInput';
    
    function App() {
      return (
        <div>
          <Counter />
          <TextInput />
        </div>
      );
    }
    
    export default App;

    5.実装結果

    この構成でアプリを実行すると、カウントアップと入力フォームが動作するシンプルなアプリが完成します。※以下のイメージ図はCSSを実装しています。

    6.ファイルを分けてコードや状態を一元管理することのメリット

    • 可読性の向上

      各機能を役割ごとに分けることで、コードが整理されて見通しが良くなります。

    • 状態管理のしやすさが向上

      状態をグローバルに管理できるため、複数のコンポーネント間で効率的に状態を共有できます。

    コードを実際に試してみることで、Zustandのシンプルさと使いやすさを実感していただけたかと思います。次の章では、Zustandの機能をさらに拡張するための「ミドルウェア」の活用方法について解説していきます。ミドルウェアを使うことで、状態管理がさらに便利になります。

    Zustandのミドルウェアの紹介

    Zustandには便利なミドルウェアがいくつかありますが、ここでは「persist」について解説します。

    persistとは?

    persistは、Zustandの状態をlocalStorageに保存(永続化)できるミドルウェアです。これにより、状態が保持され、ページをリロードしたりブラウザを閉じても、前回の状態を復元できます。例えば、以下のような場面で役立ちます。

    • ユーザーの操作履歴を保持したい場合
    • 設定情報(テーマや言語設定など)を保存したい場合

    localStorageとは?

    localStorageは、ブラウザ内でデータを保持するための仕組みで、以下の特徴があります。

    • データは「キー」と「値」のペアで管理

      例: キーが'app-storage'、値が{ count: 5, text: 'Hello' }のように保存されます。

    • データは削除しない限り永続的に保持

      ページリロードやブラウザ再起動後もデータが残ります。

    ハンズオンで作成したアプリに、persistを使って状態の保存機能を追加してみましょう。

    1. Zustand ストアにpersistを導入

    以下のように、useStore.jsにpersistを追加します。

    // src/stores/useStore.js
    
    import { create } from 'zustand';
    import { persist } from 'zustand/middleware';
    
    const useStore = create(
      persist(
        (set) => ({
          count: 0,
          text: '',
          increase: () => set((state) => ({ count: state.count + 1 })),
          decrease: () => set((state) => ({ count: state.count - 1 })),
          setText: (newText) => set({ text: newText }),
        }),
        {
          name: 'app-storage', // localStorageに保存されるキー名
        }
      )
    );
    
    export default useStore;

    localStorage に保存されるキー名は任意で設定可能ですが、名前が重複するとデータが上書きされてしまうため、一意にするのが推奨されます。また、他の開発者や将来的なメンテナンス時に分かりやすくするため、キー名には状態の内容を示す単語を使うのが良いでしょう。

    2. 既存のコンポーネントはそのまま利用

    Counter.jsx や TextInput.jsx などのコンポーネントは、ハンズオンで作成したコードのまま使用できます。状態管理のロジックがstoreで一元管理されているため、このような変更にも柔軟に対応できることを実感できます。

    3.データが保存されているか確認

    1. アプリを実行して状態を変更

      アプリを実行し、カウントを増減したり、テキストを入力します。

    2. ブラウザの開発者ツールで確認

      開発者ツール(F12)を開き、「Application」タブを選択します。左メニューの「Storage」→ 「Local Storage」を開くと、保存されたキー(app-storage)とそのデータが表示されます。

    3. ページをリロードしてもデータが残ることを確認

      ページをリロード後もストアに保存されたデータが反映され、状態が復元されていることを確認できます。

    persistを活用することで、状態をリロード後も保持できるようになります。これにより、ユーザーが設定したサイトのテーマや、ECサイトなどのショッピングカートの状態が保持され、アプリの使いやすさを向上させます。

    ミドルウェアを使用することで、Zustandを使った状態管理がさらに便利になることを実感いただけたのではないでしょうか。

    この記事で学んだこと

    この記事では、Reactの状態管理ライブラリZustandについて、基本的な使い方を学び、ハンズオン形式で理解を深めました。最後に大切なポイントを振り返りましょう。

    Zustandは、Reactアプリ向けの軽量でシンプルな状態管理ライブラリです。複雑になりがちなアプリの状態を一元管理し、コンポーネント間で簡単に共有できるようにすることで、コードの可読性や保守性を向上させることができます。

    また、Zustandの便利なミドルウェアpersistを活用することで、状態をlocalStorageに保存し、リロードやブラウザの再起動後も前回の状態を復元できる仕組みについて学びました。

    状態管理ライブラリは難しいというイメージを持たれがちですが、Zustandはそのシンプルさゆえに初心者にも扱いやすいのが特徴です。この記事を通じてZustandへの理解を深めるとともに、新しい知識を実際のプロジェクトで活用するきっかけとなれば幸いです。

    ぜひ、まずは小さなアプリケーションから実践してみてください。その一歩が、確実に成長へとつながります。

    参考資料

    以下のリンクは、この記事で説明した手順や概念に関連する参考資料です。より詳しく学びたい方は、ぜひご覧ください。

    【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

    IT未経験者必見 USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話

    プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。

    「フリーランスエンジニア」

    近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。

    「成功する人とそうでない人の違いは何か?」

    私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。

    比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。

    多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、

    note記事3000いいね超えの殿堂記事 LINE登録で記事を見る

    エンベーダー編集部

    エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。

    RareTECH 無料体験授業開催中! オンラインにて実施中! Top10%のエンジニアになる秘訣を伝授します! RareTECH講師への質疑応答可

    関連記事