4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

状態管理ライブラリのzustand使ってみた

Posted at

はじめに

zustandはコンパクトなReactで動作する状態管理ライブラリです。
ミドルウェアで必要な分だけ機能拡張も可能です。

Redux、Recoilなど他の状態管理ライブラリと比較すると
最初に覚える概念が少なくContext.Providerでラップしなくてよかったりと
導入しやすいライブラリだと思います。

今回はzustandを使ってみたまとめになります。

インストール

npm install zustand

基本的な使い方

ストアを作成してそれを呼び出すだけなのでとても簡単です。

ストア作成

interface BearState {
  bears: number
  increase: () => void
}

export const useBearStore = create<BearState>()((set) => ({
  bears: 0,
  increase: () => set((state) => ({ bears: state.bears + 1 })),
}))

コンポーネントにバインド

function Controls() {
  const increase = useBearStore((state) => state.increase)
  return <button onClick={increase}>one up</button>
}

複数の状態を持っている場合の取得

個別に取得

基本的には必要な状態のみ取得するようにします。
そうすることで不要な再レンダリングを防げます。

const nuts = useBearStore((state) => state.nuts)
const honey = useBearStore((state) => state.honey)

複数の状態を同時に取得

ネストしていないフラットな複数の状態を同時に取得する場合はshallowを指定することで
指定されていない状態が更新されたときの再レンダリングを防ぐことが出来ます。

shallowを指定しない場合useBearStoreにbearsというもう一つの状態があり更新されたとき
下記も再レンダリングされます。

const { nuts, honey } = useBearStore(
  (state) => ({ nuts: state.nuts, honey: state.honey }),
  shallow
)

再レンダリングをより詳細に制御するには、任意のカスタム等値関数を作成します。

const treats = useBearStore(
  (state) => state.treats,
  (oldTreats, newTreats) => compare(oldTreats, newTreats)
)

状態の更新

ReactのuseStateと同様に自動的な更新オブジェクトのマージを行いません。
下記のuseStateの例のようにマージしたい場合、スプレッド構文と併用します。
zustandのset関数も同様に状態を保持する必要があります。

const [state, setState] = useState({});
setState(prevState => {
  return {...prevState, ...updatedValues};
});

zustandでストアを作成する時の典型的な例では下記のような形になります

import { create } from 'zustand'

const useCountStore = create((set) => ({
  count: 0,
  inc: () => set((state) => ({ count: state.count + 1 })),
}))

set関数は保持している値にマージするために本来はこのような形になるはずです。

set((state) => ({ ...state, count: state.count + 1 }))

ただし、これは一般的なパターンであるため
...state部分をスキップできるので最終的に下記だけになります。

set((state) => ({ count: state.count + 1 }))

ネストされたオブジェクト

set関数は、1つのレベルでのみ状態をマージします。
ネストされたオブジェクトがある場合は、それらを明示的にマージする必要があります。
例えば次のようにスプレッド演算子を使用します。

import { create } from 'zustand'

const useCountStore = create((set) => ({
  nested: { count: 0 },
  inc: () =>
    set((state) => ({
      nested: { ...state.nested, count: state.nested.count + 1 },
    })),
}))

さらに深くネストした状態を持っている場合はスプレッド演算子を各レベルで書く必要があり
冗長でコードが長くなるのでimmerミドルウェア等を利用します。

ベストプラクティス

1つの大きなストアを作りその中に小さなストアを設置するパターンが推奨されています。

export const createFishSlice = (set) => ({
  fishes: 0,
  addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})

export const createBearSlice = (set) => ({
  bears: 0,
  addBear: () => set((state) => ({ bears: state.bears + 1 })),
  eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
})
import { create } from 'zustand'
import { createBearSlice } from './bearSlice'
import { createFishSlice } from './fishSlice'

export const useBoundStore = create((...a) => ({
  ...createBearSlice(...a),
  ...createFishSlice(...a),
}))

まとめ

zustandは簡単に導入出来てとても使いやすいと感じました。
気軽に導入出来る分、使い方を工夫しないと予期しない状態変更になったりすると思うので
プロジェクト内で使い方を予め定義して導入すると良さそうです。
引き続きzustandの情報収集はやっていきたいと思っています。

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?