KENTEM TechBlog

建設業のDXを実現するKENTEMの技術ブログです。

TypeScriptでZustandを使う方法と注意点

こんにちは。フロントエンドエンジニアのM・Sです。

皆さんは状態管理ライブラリは何を使っていますか?

私のプロジェクトではZustandを採用しています。

Zustandはシンプルで柔軟な状態管理ライブラリです。

Reactにおけるグローバルな状態の管理に適しており、Reduxのような冗長さを避けつつ、必要な機能を抑えています。

この記事では、TypeScriptでZustandを使う際に最低限知っておくべきことと注意点を紹介します。

基本的な使い方

基本的な使い方は、状態や更新関数を含んだ状態オブジェクトをcreate()で定義し、それを各コンポーネントで呼び出すだけです。

ストアをexportしてしまえばどのコンポーネントでも使えるようになります。

useContextContext.Providerなどの定義は不要なので、実装コストが非常に低いです。

// javascript
import { create } from 'zustand'

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

function Counter() {
  const { count, inc } = useStore()
  return (
    <div>
      <span>{count}</span>
      <button onClick={inc}>one up</button>
    </div>
  )
}

TypeScriptでの使い方

ほぼ同じように書けますが、TypeScriptでZustandを使う際は注意点があります。

それは、「create()のカリー化」「ジェネリクスによる型定義」が必要なことです。

// typescript
import { create } from 'zustand'

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

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

カリー化について

カリー化とは、複数の引数を取る関数を、1つずつ引数を受け取る関数の連鎖に変換することです。

カリー化された関数は、func()(...)のように実行します。

const useBearStore = create<BearState>()((set) => ({})

今回で言うと、createは2通りの戻り値を持っています。

  • 引数にストアの定義が入っていた場合、そのままcreateImpl()という関数の戻り値を返す

  • 引数がundefinedの場合、createImpl()自体を返す

実装箇所:https://github.com/pmndrs/zustand/blob/main/src/react.ts

なぜカリー化をする必要がある?

現状のTypeScriptでは、複数のジェネリクスを受け取る時に一部の型推論が行われません(TypeScript #10571)。

これを解消するのに有効なのがカリー化です。

カリー化を行うことで、ストアの型を明示的にしてストアの定義ができるようになります。

ちなみにカリー化をしなくてもストアの定義自体はできますが、そのままmiddlewareを使おうとするとエラーになります。

(詳しくはWhy the curreyng ()(...)?

値を永続化して保持する

「persist」というmiddlewareを使うことで、値をローカルストレージに保持することができます。

これにより、リロードやページ遷移しても値が初期化されずに済みます。

const useBearStore = create<BearState>()(
    persist(
      (set) => ({
        bears: 0,
        increase: (by) => set((state) => ({ bears: state.bears + by })),
      }),
      { name: 'bearStore' }
  )
)

ローカルストレージに値が格納されていることが確認できる

他にもdevtools, combine, immerといったmiddlewareがあるので、興味のある方は調べてみてください!

まとめ

以上、Zustandのご紹介でした。

直感的で簡単に実装できるので、個人開発から中規模・大規模開発でも活躍できるのではないかなと思います。

Zustandの内部構造についても理解を深めてアウトプットしたいと考えていますが、それはもう少し先の話になりそうです。

おわりに

KENTEMでは、様々な拠点でエンジニアを大募集しています! 建設×ITにご興味頂いた方は、是非下記のリンクからご応募ください。 recruit.kentem.jp career.kentem.jp