전역상태 라이브러리 zustand를 이용하여 전역 상태 관리하기

sumi-0011·2023년 6월 13일
1

📓 코드 저장소

목록 보기
1/4
post-thumbnail

Zustand?

  • 전역 스토어를 제공하며 셀럭터를 포함한 간단한 API를 포함합니다.
  • 용량도 매우 작습니다.(1.1kb)
  • 개념적으로 리덕스와 비슷합니다. (store을 이용한 전역상태관리)
  • 사용중인 필드를 셀렉터로 추적해야 합니다.

Zustand 사용 팁 ⭐️

  • 반드시 커스텀 훅만 export

    전체 스토어를 구독하여 불필요한 리렌더링이 많이 일어난다.

  • Zustand는 여러 개의 작은 스토어를 가질 것을 권장한다.
  • 액션과 상태는 분리한다.

    액션을 구조 분해하고 그 중 하나만 사용한다.

const useBearStore = create((set) => ({
  bears: 0,
  fish: 0,
// ⬇️ separate "namespace" for actions
actions: {
    increasePopulation: (by) =>
      set((state) => ({ bears: state.bears + by })),
    eatFish: () => set((state) => ({ fish: state.fish - 1 })),
    removeAllBears: () => set({ bears: 0 }),
  },
}))

export const useBears = () => useBearStore((state) => state.bears)
export const useFish = () => useBearStore((state) => state.fish)

// 🎉 one selector for all our actions
export const useBearActions = () => useBearStore((state) => state.actions)

나의 구현 코드

사용 팁을 적용하여 구현

import { create } from 'zustand';

import { getMyReviewAPI } from '@/api/me';
import { getRecommendRestaurantAPI } from '@/api/recommend';
import {
  restaurantReviewCreate,
  restaurantReviewDelete,
  restaurantReviewUpdate,
} from '@/api/reviews';
import type { RestaurantType } from '@/api/types';
import type { DistrictCodeType } from '@/constants/position';

interface BearState {
  selectItem: RestaurantType | null;
  recommendList: RestaurantType[] | null;
  myList: RestaurantType[];

  selectDistrict: DistrictCodeType | null;

  fetch: {
    fetchRecommendList: () => Promise<RestaurantType[]>;
    fetchMyList: () => Promise<RestaurantType[]>;
  };

  actions: {
    setSelectItem: (item: RestaurantType | null) => void;
    initSelectItem: () => void;

    setSelectDistrict: (districtCode: DistrictCodeType | null) => void;

    setRecommendList: (list: RestaurantType[] | null) => void;
    setMyList: (list: RestaurantType[]) => void;

    reviewCreate: (id: number, newRating: number) => Promise<void>;
    reviewUpdate: (id: number, newRating: number) => Promise<void>;
    reviewDelete: (id: number) => Promise<void>;
  };

  _reload: {
    selectItemReload: () => Promise<void>;
  };
}

const useRestaurantStore = create<BearState>((set, get) => ({
  selectItem: null,
  recommendList: null,
  myList: [],

  selectDistrict: null,
  fetch: {
    fetchRecommendList: async () => {
      const districtCode = get().selectDistrict;

      if (!districtCode) return [];

      const recommendList = await getRecommendRestaurantAPI(districtCode);
      get().actions.setRecommendList(recommendList);

      return recommendList;
    },
    fetchMyList: async () => {
      const list = await getMyReviewAPI();
      get().actions.setMyList(list);

      return list;
    },
  },
  _reload: {
    selectItemReload: async () => {
      const districtCode = get().selectDistrict;
      if (!districtCode) return;

      const list = await get().fetch.fetchMyList();

      const currentSelectItem = get().selectItem
        ? list.find((item) => item.id === get().selectItem?.id)
        : null;
      currentSelectItem && get().actions.setSelectItem(currentSelectItem);
    },
  },
  actions: {
    setSelectItem: (item) =>
      set(() => {
        console.log('set item: ', item);
        return { selectItem: item };
      }),
    initSelectItem: () =>
      set(() => {
        console.log('initSelectItem: ');
        return { selectItem: null };
      }),

    setSelectDistrict: (districtCode) => set({ selectDistrict: districtCode }),

    setRecommendList: (list) => set({ recommendList: list }),
    setMyList: (list) => set({ myList: list }),

    reviewCreate: async (id: number, newRating: number) => {
      try {
        await restaurantReviewCreate(id, newRating);

        get().fetch.fetchRecommendList();
        get()._reload.selectItemReload();
      } catch (error) {}
    },

    reviewUpdate: async (id: number, newRating: number) => {
      try {
        await restaurantReviewUpdate(id, newRating);

        get()._reload.selectItemReload();
      } catch (error) {}
    },
    reviewDelete: async (id: number) => {
      try {
        await restaurantReviewDelete(id);

        get()._reload.selectItemReload();
      } catch (error) {}
    },
  },
}));

export const useSelectRestaurant = () =>
  useRestaurantStore((state) => state.selectItem);

export const useRecommendList = () =>
  useRestaurantStore((state) => state.recommendList);

export const useSelectDistrict = () =>
  useRestaurantStore((state) => state.selectDistrict);

export const useMyList = () => useRestaurantStore((state) => state.myList);

export const useRestaurantActions = () =>
  useRestaurantStore((state) => state.actions);

export const useRestaurantFetch = () =>
  useRestaurantStore((state) => state.fetch);

참고 링크

profile
안녕하세요 😚

0개의 댓글