🤖

Zustandでのディレクトリ構成とAPI連携

2024/04/03に公開

はじめに

新規開発で状態管理をどのライブラリにしようかと検討したところ、定番のReduxの他にコントリビュート数が多いのと軽量でコード量が少ないところが特徴のZustandがあったので、開発するのは中規模なサービスを想定していたため選定してみました。実際にプロジェクトで使ってみた際のディレクトリ構成とAPI連携について考えていきます。

ディレクトリ構成

ディレクトリの構成としてfeatures/配下に各画面毎にディレクトリを作成します。
これは画面設計書毎にディレクトリを管理する考え方で、直感的に管理できるメリットがあります。
今回の場合、以下のようなイメージ

feature/ 
    todo/
    todo_detail/
    login/
ui/

画面毎のディレクトリの配下に画面に関わる機能を追加していきます。基本的にはfeatures/の中で依存関係を完結し、共通して使うコンポーネントについてはui/ディレクトリをfeatures/の同階層において使えるようにします。ui/features/ディレクトリに依存しないように注意します。

API連携

APIとの連携に関わるところは、hooks/配下に機能毎にファイルを作成していきます。
キャンペーン一覧の場合:

campaign/
    hooks/
          useFetchTodo.ts
          useUpdateTodo.ts
          useDeleteTodo.ts

hooksを機能毎にわけるのは、機能を一つにまとめると簡単にfatなhooksになり、ファイルの可読性が下がるためできるだけシンプルに一機能一ファイルで分けていきます。実際にAPIと連携するとこのようなコードになります。

import create from "zustand";

type Todo = {
  todoList: string[];
  fetchTodos: () => void;
  resetCount: () => void;
}

export const useTodoStore = create<Todo>((set) => ({
  todoList: [],
  fetchTodoList: async () => {
            const response = await fetch(
              "http://localhost:3000/todos",
            );
            const todoList = await response.json();
            set(() => ({ todoList: todoList }));
          },
  resetCount: () => set({ todoList: [] }),
}));

確かにコード量が少なく済んでいることがわかります。

middlreware persist(データの永続化)

また、zustandはmiddlewareのクラスにpersistがあり、状態をブラウザのlocalStorage, AsyncStorage, IndexedDBなどに保存することでリロードしてもブラウザで保持し、データを永続化することができます。

export const useStore = create<Todo>()(
  persist(((set) => ({
    todoList: [],
    fetchTodoList: async () => {
            const response = await fetch(
              "http://localhost:3000/todos",
            );
            const todoList = await response.json();
            set(() => ({ todoList: todoList }));
          },
    resetCount: () => set({ todoList: [] }),
  }),
   {
     name: " todo-store", // storageのキー名
   })
);

独自ミドルウェアを作成する

自作のミドルウェアも作成できるので、状態のlogを吐かせることもできます。

interface TodoState {
  todoList: Campaign[];
  setTodoList: (campaign: Campaign) => void;
  fetchTodoList: () => void;
}

const log = (config: any) => (set: any, get: any, api: any) =>
  config(
    (...args: any) => {
      console.log("  applying", args);
      set(...args);
      console.log("  new state", get());
    },
    get,
    api,
  );


export const useFetchTodo = create<TodoState>()(
  log((set) => ({
      todoList: [],
      setTodo: () => set((state: any) => ({ campaigns: state.todos })),
      fetchTodoList: async () => {
        const response = await fetch("http://localhost:3000/todos");
        const todos = await response.json();
        set(() => ({ todos }));
      },
    }),
  ),
);

まとめ

今回は新規開発においてのZustandの使い方とディレクトリ構成を考えてみました。Reduxと違い、かなり軽量にコードが書けることがわかりました。コード量が多くなってくると複雑化してメンテナンスが難しくなることも想定されますが、大規模なサービスではない場合は使い勝手のいいライブラリなのではないかと思います。

参考文献

https://docs.pmnd.rs/zustand/getting-started/comparison
https://tech-blog.rakus.co.jp/entry/20230208/frontend

Discussion