📑

Zustandでの状態管理が複雑になってきた時に使えるSliceパターン

2024/09/16に公開

はじめに

前回に続いてZustandの記述パターンについて備忘的に書いていきます。今回はタイトルにもあるように状態管理が複雑になってきた時に使えるSliceパターンです。基本的にZustandはシンプルな記述をすることが大きなメリットではありますが、コードが複雑になってくると段々Store内で定義したactionsやstateが大きくなってきます。zustand-slicesでは別で定義したStore同士を結合して利用することができます。

サンプルコード

import { create } from 'zustand';
import { createSlice, withSlices } from 'zustand-slices';

const countSlice = createSlice({
  name: 'count',
  value: 0,
  actions: {
    inc: () => (prev) => prev + 1,
    reset: () => () => 0,
  },
});

const textSlice = createSlice({
  name: 'text',
  value: 'Hello',
  actions: {
    updateText: (newText: string) => () => newText,
    reset: () => () => 'Hello',
  },
});

const useCountStore = create(withSlices(countSlice, textSlice));

const Counter = () => {
  const count = useCountStore((state) => state.count);
  const text = useCountStore((state) => state.text);
  const { inc, updateText, reset } = useCountStore.getState();
  return (
    <>
      <p>
        Count: {count}
        <button type="button" onClick={inc}>
          +1
        </button>
      </p>
      <p>
        <input value={text} onChange={(e) => updateText(e.target.value)} />
      </p>
      <p>
        <button type="button" onClick={reset}>
          Reset
        </button>
      </p>
    </>
  );
};

Sliceにするメリット

  • 関心の分離ができる
    • モジュール毎に細かくstoreを分けて管理することでコードの可読性の向上することができます

withActions

更にwithActionsを用いることでstoreレベルでアクションを追加することができます。これを利用することでsliceで分割した各々の値をstore内で変更することができます。

サンプルコード

import { create } from 'zustand';
import { createSlice, withSlices, withActions } from 'zustand-slices';

const countSlice = createSlice({
  name: 'count',
  value: 0,
  actions: {
    'count/inc': () => (prev) => prev + 1,
    'count/set': (newCount: number) => () => newCount,
    reset: () => () => 0,
  },
});

const textSlice = createSlice({
  name: 'text',
  value: 'Hello',
  actions: {
    'text/set': (newText: string) => () => newText,
    reset: () => () => 'Hello',
  },
});

const useCountStore = create(
  withActions(withSlices(countSlice, textSlice), {
    setCountWithTextLength: () => (state) => {
      state['count/set'](state.text.length);
    },
  }),
);

withSlicesの定義に重ねる形でwithActionsを記述し、その中にアクションを記述します。

withActionsのメリット

  • Actionsを統合して書くことができる
    • 正直この機能はかなり待ってましたな機能で、これまでモジュール毎に分割したstateを組み合わせて計算処理をしたり、配列をconcatして任意の形にしたい時にはそれぞれのstoreを一度呼び出す必要があったため煩雑になりがちでした。withActionsを利用することでstoreレベルで計算処理を行うことにより、非常にシンプルに書くことができます。これはかなり可読性の向上に繋がります。

まとめ

Zustandは基本的にはシンプルに記述できることを得意とする状態管理ライブラリですが、コードの複雑化、大規模化によって煩雑になるケースをこれらの方法で可読性の向上、スケーラビリティを担保して記述することができます。なのでZustandにおいても大規模なプロジェクトにも対応できる方法として選択肢が増えてきています。

参考資料

https://github.com/zustandjs/zustand-slices
https://x.com/dai_shi/status/1786568001044750693

Discussion