React状態管理ライブラリ Zustandのミドルウェアを使ってみた

2023.01.03

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは。データアナリティクス事業本部 サービスソリューション部の北川です。

Zustandとは

ZustandはReactの状態管理ライブラリの一つです。シンプルで使いやすく、他の状態管理ライブラリと比べてもコード量が少ないです。

基本的な使い方については、以下のエントリで書いています。

今回は、Zustandのミドルウェアを使ってみました。基本的には、公式のGitHubに沿って進めています。

Next.jsプロジェクトを作成

前回、エントリで使用したコードをそのまま使っていきます。

$ npx create-next-app sample-zustand --ts
$ yarn add zustand

// または
$ npm install zustand

libs/index.ts

import create from "zustand";

type Count = {
  count: number;
  increaseCount: () => void;
  resetCount: () => void;
}

export const useStore = create<Count>((set) => ({
  count: 0,
  increaseCount: () =>
    set((state) => {
      return { count: state.count + 1 };
    }),
  resetCount: () => set({ count: 0 }),
}));

index.ts

import type { NextPage } from "next";
import { useStore } from "../libs/store";
import styles from "../styles/Home.module.css";

const Count = () => {
  const count = useStore((state) => state.count);
  console.log(count);
  return (
    <>
      <h2>count: {count}</h2>
    </>
  );
};

const IncreaseCountButton = () => {
  const increaseCount = useStore((state) => state.increaseCount);

  return <button onClick={() => increaseCount()}>Increase</button>;
};

const ResetButton = () => {
  const { resetCount } = useStore();

  return <button onClick={() => resetCount()}>Reset</button>;
};

const Home: NextPage = () => {
  return (
    <div className={styles.container}>
      <main className={styles.main}>
        <Count />
        <IncreaseCountButton />
        <ResetButton />
      </main>
    </div>
  );
};

export default Home;

ミドルウェアを試してみる

Zustandが用意しているいくつかのミドルウェアは、"zustand/middleware"からインポートすることで使用できます。

devTools

devToolsミドルウェアを使用すると、拡張機能のRedux DevToolsを使えるようになります。 Redux DevToolsに関しては、こちらのエントリで書いています。

setの第3引数に、それぞれのactionTypeを渡すことができます。

libs/index.ts

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

type Count = {
  count: number;
  increaseCount: () => void;
  resetCount: () => void;
};

export const useStore = create<Count>()(
  devtools((set) => ({
    count: 0,
    increaseCount: () =>
      set(
        (state: { count: number }) => {
          return { count: state.count + 1 };
        },
        false,
        "increaseCount"
      ),
    resetCount: () => set({ count: 0 }, false, "resetCount"),
  }))
);

immer

immerはオブジェクトをイミュータブルに扱うためのライブラリになります。

developersIOでも、以下のエントリが投稿されています。

immerミドルウェアを使用すると、push()やunshift()などの破壊的なメソッドを使用したstateの変更ができるようになります。このミドルウェアは、"zustand/middleware/immer"からインストールします。

こちらは、公式のimmerミドルウェアを使ったコードです。

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

const useBeeStore = create(
  immer((set) => ({
    bees: 0,
    addBees: (by) =>
      set((state) => {
        state.bees += by
      }),
  }))
)

自作ミドルウェア

提供されているミドルウェアではなく、自作のミドルウェアも作成できます。Zustandのデモでは、stateの変更を記録し、ログとして出力するミドルウェアを定義しています。

libs/index.ts

import create from "zustand";

type Count = {
  count: number;
  increaseCount: () => void;
  resetCount: () => void;
};

const log = (config) => (set, get, api) =>
  config(
    (...args) => {
      console.log("  applying", args);
      set(...args);
      console.log("  new state", get());
    },
    get,
    api
  );

export const useStore = create<Count>()(
  log((set) => ({
    count: 0,
    increaseCount: () =>
      set((state: { count: number }) => {
        return { count: state.count + 1 };
      }),
    resetCount: () => set({ count: 0 }),
  }))
);

複数ミドルウェアを使う

もちろん、以下のように記述することで、複数のミドルウェアを使用できます。

export const useStore = create<Count>()(
  log(
    devtools((set) => ({
      count: 0,
      increaseCount: () =>
        set(
          (state: { count: number }) => {
            return { count: state.count++ };
          },
          false,
          "increaseCount"
        ),
      resetCount: () => set({ count: 0 }, false, "resetCount"),
    }))
  )
);

まとめ

今回は、Zustandのミドルウェアを簡単にですが、試してみました。Zustaindでは他にも、再現が難しかったのですが、stateの状態を保持するPersistミドルウェアなどもあります。

ミドルウェアは学ぶことが多くまだまだ勉強中です。これからもインプット、アウトプットに力を入れていきたいと思います。

ではまた。