LoginSignup
25
6

zustandを初めて使ってみた

Last updated at Posted at 2023-06-19

はじめに

reactの状態管理ライブラリのzustandを今更ながら初めて知り、以前recoilを使っていましたがzustandの方がシンプルに状態管理できそうだったのでキャッチアップ&実務で活用してみました。
今回はzustandの大まかな紹介になります。

zustandとは

公式ドキュメント:https://zustand-demo.pmnd.rs/

冒頭でも軽く説明していますが、Zustandは状態管理ライブラリであり小さくシンプルに管理することができる。
またミドルウェアのサポート(永続化、デバック、非同期など)がいくつか提供されている。
そしてアプリケーション全体にアクセスできるグローバルステートの管理ができます。

zustandはドイツ語で「状態」を表している。開発元はドイツの「Poimandres」という会社でJSのライブラリをいくつか開発しており、zustandと同じ状態管理ライブラリのJotalやアニメーションライブラリのReact Springなどあります。

ちょっとした歴史

2019年頃に開発されておりReact hooksを活用してシンプルで直感的なstate管理を提供することを目指していた。
また軽量ライブラリの中ではこの一年で使用率が伸びており比較人気が上がっているのもみれたり、最新バージョンはv4.3.8(2023/6/12時点)と定期的にメンテナンスがされていて手厚い。
以下は最新のnpmトレンドのものです。

image.png

(Reduxは相変わらず人気があり、ダウンロード数で言えばzustandの4倍近くありました。)

使用例

zustandのcreateメソッドを使うことで状態管理のストアを作成することができる。
stateの初期値の設定とcreateメソッドに以下の関数が備わっています。

  • get(): storeの値を参照する
  • set(): 状態を更新する

上記があるのでstoreの中で状態と更新ロジックをstoreの中で作成することができます。

create-example.ts

import { create } from 'zustand'

// stateの定義と更新ロジックを含むストアを作成。
const useStore = create(set => ({
  count: 0,
  increase: () => set(state => ({ count: state.count + 1 })),
  decrease: () => set(state => ({ count: state.count - 1 }))
}))

// ストアをコンポーネントで使用
function Counter() {
  const { count, increase, decrease } = useStore()
  return (
    <div>
      <button onClick={decrease}>-</button>
      <span>{count}</span>
      <button onClick={increase}>+</button>
    </div>
  )
}


また、ストアの外で状態の更新を定義することが可能です。


import { create } from "zustand";

const useStore = create(set => ({
  count: 0,
  increase: () => set(state => ({ count: state.count + 1 })),
  decrease: () => set(state => ({ count: state.count - 1 }))
}))

+ export const reset = () => useStore.setState({ count: 0 });


store利用の注意点

ストアのstateを丸ごと呼び出すと変更があるたびに再レンダリングされてしまうので利用する時は注意する。
なのでstoreを使う時は、state毎の指定が必要になります。

storeを利用する時には使いたいstate、set関数をセレクターを引数に定義することで、必要な状態のみを返す形になります。

create-example.ts

import { create } from 'zustand'


const useStore = create(set => ({
  count: 0,
  increase: () => set(state => ({ count: state.count + 1 })),
  decrease: () => set(state => ({ count: state.count - 1 })),
}))

// increaseのみを購読する形になる。
function CountUp() {
  const increase = useStore((state) => state.increase)

  return (
    <div>
       <button onClick={increase}>+</button>
    </div>
  )
}


Zustand内で非同期処理を使ってアクションもすることが可能です。
例えばデータ取得で呼び出すfetchとステートの変更と一緒に使用することも可能です。

fetch-example.ts

import create from 'zustand'

const useStore = create((set) => ({
  data: null,
  loading: false,
  error: null,
  getData: async () => {
    set({ loading: true })
    try {
      const res = await fetch('https://example.com/data')
      const data = await res.json()
      set({ data, loading: false })
    } catch (error) {
      set({ error, loading: false })
    }
  }
}))

// コンポーネント内で使用
function MyComponent() {
  const { data, loading, error, getData } = useStore()

  ///
}


presist 永続化

persistとというstateを永続化するためのミドルウェアがあります。storeを作る時にpersistを使うことでstateをlocalStorageに保存してブラウザがリロードされてもstateを保持することができます。

使う時はcreate内のstoreをpersistでラップして、ストレージに保存するKeyのnameを追加する必要がある。
デフォルトでは保存先がlocalStorageとなっていますが任意のStorageを指定することも可能。

persist-example.ts

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

const useStore = create(
  persist(
    set => ({
      count: 0,
      increase: () => set(state => ({ count: state.count + 1 })),
      decrease: () => set(state => ({ count: state.count - 1 })),
    }),
    {
      name: 'count-store', // storageのキー名
      // getStorage: () => sessionStorage, // カスタムストレージを指定することも可能。
    }
  )
)


devtool

devtoolも提供されており、zustandの状態やアクションのトラッキング、デバックなどができます。
使う場合はcreate内のstoreをdevtools()でラップして使います。
拡張機能についてはRedux Developer Toolsで確認することができます。

devtool.ts

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

const useStore = create(
  devtools(set => ({
    count: 0,
    increase: () => set(state => ({ count: state.count + 1 })),
    decrease: () => set(state => ({ count: state.count - 1 })),
  }))
)

実際にカウントのstateをRedux Developer Toolsで確認できました。
スクリーンショット 2023-06-19 0.39.26.png

その他

スライスパターン

スライスパターンという形で個々の状態と更新を小さなstoreとしてを分割し利用することも可能です。
以下ではcountcolorのstateを分けてcreate()で集約させている形にしています。

slice-exmaple.ts

import { create, StateCreator } from 'zustand'

const createCountStore: StateCreator = () => ({
  count: 0,
  increment: () => set(state => ({ count: state.count + 1 })),
  decrement: () => set(state => ({ count: state.count - 1 })),
});

const createColorStore: StateCreator = () => ({
  color: 'blue',
  setColor: (color) => set({ color }),
});

const useStore = create(set => ({
  ...createCountStore(set),
  ...createColorStore(set),
}))

// コンポーネント内で使用
function Counter() {
  const { count, increment, decrement } = useStore();

  ///
}

function Color() {
  const { color, setColor } = useStore();

  ///
}


様々なサードパーティツール

  • simple-zustand-devtools: react-devtoolでストアの状態を確認することが出来る。
  • zustand-forms: formのstateやバリデーションをストアで管理できる。
  • vue-zustand: Vueでzustandを扱える(Angularやsoild.js対応もあった)

zustandの良かったところ

  • シンプルで簡潔的なストア管理ができる
  • 柔軟な状態の管理
  • Devtoolやpersistなどのミドルウェアのサポート
  • TypeScriptのサポート
  • React以外のフレームワークでも使用が可能
25
6
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
25
6