Open8

Zustandメモ

RerrahRerrah

Reduxのようにグローバルなstoreを操作する.しかしstate, reducer, actioin, selector, dispatcherなどに分かれておらず,stateにメソッドのような形でreducer (action) を定義してstoreを作成する.

import { create } from "zustand";

type State = {
  count: number;
};

type Action = {
  countUp: () => void;
};

type Store = State & Action;

const useStore = create<Store>()((set) => ({
  // stateの初期値
  count: 0,

  // set() で受け取るアロー関数でstateを更新する
  // アロー関数の引数は現在のstate
  // アロー関数の戻り値のオブジェクトのプロパティがstateに差分として書き加えられる
  countUp: () => set((state) => ({ count: state.count + 1 })),
}));

// create()の戻り値がselectorのhookとなっている
const count = useStore((state) => state.count);
const countUp = useStore((state) => state.countUp);

<button type="button" onClick={countUp}>{count}</button>
RerrahRerrah

middlewareを定義することもできる.immerをmiddlewareとして使うと,stateの更新がもう少し楽に書ける.

const useStore = create<Store>()(immer((set) => ({
  count: 0,
  countUp: () => set((state) => ++state.count),
})));
RerrahRerrah

Reduxのdispatch(actionCreator(args))みたいな書き方ではなく,直接Actionの関数を呼び出せる(countUp()みたいな感じ)ので楽.

RerrahRerrah

1アプリケーションに複数のstoreを保持できる.
storeにactionを紐付ける点で、1つのstoreとreducerで全てのイベントを扱うReduxの考えとは異なる.
Fluxに近い?

RerrahRerrah

Slice pattern

小さなstore (slice) をマージして大きなstoreを作る.slice間の状態に応じたactionも定義可能.

Sample
type AgeSlice = {
  age: number;
  grow: () => void;
};

const createAgeSlice: StateCreator<
  AgeSlice & NameSlice,
  [],
  [],
  AgeSlice
> = (set, get) => ({
  age: 20,
  grow: () => set({ age: get().age + 1 }),
});

type NameSlice = {
  name: string;
};

const createNameSlice: StateCreator<
  AgeSlice & NameSlice,
  [],
  [],
  NameSlice
> = () => ({
  name: "Taro",
});

type SharedSlice = {
  func: () => void;
};

const createSharedSlice: StateCreator<
  AgeSlice & NameSlice,
  [],
  [],
  SharedSlice
> = (_, get) => ({
  func: () => {
    get().grow();
  },
});

const useStore = create<AgeSlice & NameSlice & SharedSlice>()((...a) => ({
  ...createAgeSlice(...a),
  ...createNameSlice(...a),
  ...createSharedSlice(...a),
}));

https://docs.pmnd.rs/zustand/guides/slices-pattern

RerrahRerrah

Reduxとは異なり,Providerは不要.
テストを書くときReduxだとコンポーネント内でStoreを参照しているときは,あらかじめテスト用にstoreを設定したカスタムProviderを定義したうえでtesting-libraryのrender()を使用するけど,Zustandだと単にrender()を使うだけで問題ないのが便利.