최상위 컴포넌트에 몰린 상태, zustand로 개선하기

khundi·2023년 10월 8일
0
post-thumbnail

문제

  1. 최상위 컴포넌트(게시글 작성 페이지)에 입력창마다 상태를 가지고 handler까지 많은 양의 코드를 가지고 있다.(약 60줄 가량)
  2. 상위 컴포넌트가 상태를 들고 있으니 전부 다 props로 전달해야하고 또 하위의 하위 컴포넌트로 props를 가지고 있는 drilling(뭐 2-4번이긴 하지만)까지 생김.

원인

submit 핸들러로 인하여 컴포넌트 별로 각자의 역할(상태)을 나누지 못하고 최상위 컴포넌트가 모든 역할을 도맡아하고 있다.

해결 방법

  1. Context API를 활용하여 상태를 분리한다.
    -> 최상위 컴포넌트에서 상태를 분리하여 목표를 달성
    -> 하지만 provider를 전체로 감싸게 되어 리렌더 발생

  2. zustand를 활용하여 상태를 분리한다.
    -> 상태를 분리함과 동시에 selector 함수를 활용하여 불필요한 리렌더 방지

결과

zustand를 활용하여 상태와 handler 함수들을 모두 분리해낼 수 있었다.

  • 최상위 컴포넌트엔 상태가 전혀 없고 렌더링 부분만 남길 수 있게 되었고, 하위 컴포넌트에서 각자의 역할에 충실할 수 있게 되었다.
export const PostProductModal: React.FC = () => {
  return (
    <FullScreenModalSheet>
      <Title />
      <Inputs />
      <EditBar />
    </FullScreenModalSheet>
  );
};
const initialState = {
  imageId: 0,
  images: null,
  thumbnailId: null,
  title: '',
  price: '',
  content: '',
  selectCategory: null,
  isCategoryListModalOpen: false,
};

export const usePostProductModalStore = create<PostProductModalStore>()(
  (set, get) => ({
    ...initialState,
    getImageIdAndIncrement: () => {
      const { imageId, incrementImageId } = get();
      incrementImageId();

      return imageId;
    },
    incrementImageId: () => set(({ imageId }) => ({ imageId: imageId + 1 })),
    addImage: (image) => {
      const { getImageIdAndIncrement } = get();
      const id = getImageIdAndIncrement();

      set(({ images }) =>
        images
          ? { images: [...images, { ...image, id }] }
          : { images: [{ ...image, id }] },
      );
    },
    deleteImage: (id: number) => {
      set(({ images }) =>
        images
          ? { images: images.filter((image) => image.id !== id) }
          : { images },
      );
    },
    setThumbnailId: (thumbnailId) => set(() => ({ thumbnailId })),
    setTitle: (title) => set(() => ({ title })),
    setPrice: (price) => set(() => ({ price })),
    setContent: (content) => set(() => ({ content })),
    setSelectCategory: (category) => set(() => ({ selectCategory: category })),
    openCategoryListModal: () => set(() => ({ isCategoryListModalOpen: true })),
    closeCategoryListModal: () =>
      set(() => ({ isCategoryListModalOpen: false })),
    getPostProductParams: () => {
      const { images, thumbnailId, title, price, content, selectCategory } =
        get();
      const { currentRegion } = useUserStore.getState();

      const thumbnailImage = images!.find(({ id }) => id === thumbnailId)!
        .imageFile!;
      const imagesWithoutThumbnail = images!
        .filter(({ id }) => id !== thumbnailId)
        .map(({ imageFile }) => imageFile);

      return {
        thumbnailImage,
        images: imagesWithoutThumbnail,
        title,
        price,
        content,
        region: currentRegion.addressName,
        status: '판매중',
        categoryId: selectCategory!.id,
        categoryName: selectCategory!.name,
      };
    },
    canSubmit: () => {
      const { images, title, selectCategory, thumbnailId } = get();

      return !(
        title &&
        selectCategory?.id &&
        images?.some(({ id }) => id === thumbnailId)
      );
    },

    reset: () => set({ ...initialState }),
  }),
);

추가로 고민해볼 점

  1. controlled하게 관리하지 않아도 될 컴포넌트라면 상태를 따로 두지 않고 form 태그를 활용하면 상태를 줄일 수 있을 것 같다.
profile
안녕하세요. 웹 프론트엔드 개발자 전성훈입니다.

0개의 댓글