hyojin-k 님의 상세페이지[3팀 김효진] Chapter 2-2. 디자인 패턴과 함수형 프로그래밍 🦍

https://hyojin-k.github.io/front_6th_chapter2-2/index.advanced.html

과제의 핵심취지

  • React의 hook 이해하기
  • 함수형 프로그래밍에 대한 이해
  • 액션과 순수함수의 분리

과제에서 꼭 알아가길 바라는 점

  • 엔티티를 다루는 상태와 그렇지 않은 상태 - cart, isCartFull vs isShowPopup
  • 엔티티를 다루는 컴포넌트와 훅 - CartItemView, useCart(), useProduct()
  • 엔티티를 다루지 않는 컴포넌트와 훅 - Button, useRoute, useEvent 등
  • 엔티티를 다루는 함수와 그렇지 않은 함수 - calculateCartTotal(cart) vs capaitalize(str)

기본과제

  • Component에서 비즈니스 로직을 분리하기

  • 비즈니스 로직에서 특정 엔티티만 다루는 계산을 분리하기

  • 뷰데이터와 엔티티데이터의 분리에 대한 이해

  • entities -> features -> UI 계층에 대한 이해

  • Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?

  • 주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?

  • 계산함수는 순수함수로 작성이 되었나요?

  • Component에서 사용되는 Data가 아닌 로직들은 hook으로 옮겨졌나요?

  • 주어진 hook의 책임에 맞도록 코드가 분리가 되었나요?

  • 계산함수는 순수함수로 작성이 되었나요?

  • 특정 Entitiy만 다루는 함수는 분리되어 있나요?

  • 특정 Entitiy만 다루는 Component와 UI를 다루는 Component는 분리되어 있나요?

  • 데이터 흐름에 맞는 계층구조를 이루고 의존성이 맞게 작성이 되었나요?

심화과제

  • 재사용 가능한 Custom UI 컴포넌트를 만들어 보기

  • 재사용 가능한 Custom 라이브러리 Hook을 만들어 보기

  • 재사용 가능한 Custom 유틸 함수를 만들어 보기

  • 그래서 엔티티와는 어떤 다른 계층적 특징을 가지는지 이해하기

  • UI 컴포넌트 계층과 엔티티 컴포넌트의 계층의 성격이 다르다는 것을 이해하고 적용했는가?

  • 엔티티 Hook과 라이브러리 훅과의 계층의 성격이 다르다는 것을 이해하고 적용했는가?

  • 엔티티 순수함수와 유틸리티 함수의 계층의 성격이 다르다는 것을 이해하고 적용했는가?

과제 셀프회고

과제를 하면서 내가 제일 신경 쓴 부분은 무엇인가요?

이번주 과제에서는 AI 디톡스를 목표로 진행했습니다. 지난 주와는 달리 리액트로 되어있고 난 더러운 코드에 익숙해져 있고~~(하지만 익숙하기 때문에 더 어려울 수 있다는 걸 왜 몰랐지?)~~ ㅎㅎ 그래서 지금까지 AI와 나는 한몸이었지만 이번주만큼은 헤어져보겠다! 라고 목표를 가지고 주체적으로 과제를 진행했던 것 같습니다. (물론 완전히 헤어지진 못함)

컴포넌트와 커스텀 훅 분리하기 시작은 컴포넌트와 커스텀 훅을 분리하는 것이었습니다. 우선 큰 단위로 header,product, cart 등을 분리하면서 같이 분리가 필요한 utils들도 같이 분리해나갔습니다. 이 시점에서는 구조나 로직을 크게 생각하지 않고 화면상에서의 구조를 분리하는 방식으로 진행했습니다. 그런데 일단 냅다 분리하고 보니까 이 사단이 나버렸습니다…wow… 스크린샷 2025-08-05 오전 9 35 06 근데 사실 그렇게까지 당황스럽진 않은게 실제로 회사에서 이런 코드들이 생각보다 많이 존재하거든요. 과제가 아니었다면 대충 돌아가는대로 냅뒀을수도 있었을테지만 주제의 본분을 잃지않고 클린코드를 위해 우선 최대한 props를 줄이는 방향으로 진행해보았습니다. 목표는 AdminPage와 MainPage에 공통으로 넘기는 props만 남길 수 있도록 해보기 위해 다음과 같이 진행했습니다.

  1. 계산로직 분리 하기

hooks 내부에 있는 계산 로직들을 공통 utils로 분리했습니다. 분리하면서 순수 함수로써 동작할 수 있도록 구현했습니다.

  1. hooks끼리의 의존성 없애기

초기에 위 처럼 props를 많이 내렸어야만 했던 이유는 hook끼리의 의존성이 엮여있었기 때문입니다. useCart에서 리턴한 cart를 useCoupon에 props로 넘겨주어야하는 등의 이슈를 최대한 제거하고 각각의 hook은 필요한 컴포넌트에서만 호출해서 사용할 수 있도록 했습니다.

아마 이 과정들을 큰 단위로 분리하기 전에 먼저 진행했으면 애초에 문제 사항이 제거되어 위와 같은 현상은 벌어지지 않았을 것 같습니다. 아무튼 결과는…

    <main className="max-w-7xl mx-auto px-4 py-8">
      {isAdmin ? (
        // 관리자 대시보드
        <AdminPage
          products={products}
          setProducts={setProducts}
          cart={cart}
          coupons={coupons}
          setCoupons={setCoupons}
          addNotification={addNotification}
        />
      ) : (
        // 쇼핑몰 메인 페이지
        <MainPage
          products={products}
          cart={cart}
          setCart={setCart}
          coupons={coupons}
          setCoupons={setCoupons}
          searchTerm={searchTerm}
          setSearchTerm={setSearchTerm}
          debouncedSearchTerm={debouncedSearchTerm}
          addNotification={addNotification}
        />
      )}
    </main>

폴더 구조에 대한 고민

├── basic/       
│   ├── App.tsx
│   ├── main.tsx
│   ├── components/
│   │   ├── Header.tsx
│   │   ├── Notification.tsx
│   │   ├── index.tsx
│   │   ├── main/
│   │   │   ├── MainPage.tsx
│   │   │   ├── Product.tsx
│   │   │   ├── Cart.tsx
│   │   │   ├── Payment.tsx
│   │   │   └── Coupon.tsx
│   │   └── admin/
│   │       ├── AdminPage.tsx
│   │       ├── AdminProduct.tsx
│   │       └── AdminCoupon.tsx
│   ├── hooks/
│   │   ├── useProduct.ts
│   │   ├── useCart.ts
│   │   ├── useCoupon.ts
│   │   ├── usePayment.ts
│   │   ├── useLocalStorage.ts
│   │   ├── useDebounce.ts
│   │   ├── useNotification.ts
│   │   └── index.ts
│   ├── types/
│   │   ├── product.ts
│   │   ├── cart.ts
│   │   ├── notification.ts
│   │   └── index.ts
│   ├── utils/
│   │   ├── productUtils.ts
│   │   ├── cartUtils.ts
│   │   ├── formatters.ts
│   │   └── index.ts
│   ├── constants/
│   │   ├── product.ts
│   │   ├── coupon.ts
│   │   └── index.ts
│   └── �� __tests__/   

사실 이 구조는 제가 익숙하게 사용하고 있는 폴더구조입니다. (좀 더 익숙한건 각 컴포넌트에서 사용하는 hooks, types도 각 컴포넌트의 내부에 포함되어 있는 형식) 그리고 실제로 힌트에 있는 폴더구조도 비슷하게 되어있었구요. 그런데 말입니다… 이번 과제에서 가장 자주 언급된 ‘entity’를 기반으로 짜여진 구조라는 생각이 안들더라고요. 그래서 다음과 같은 방식으로 폴더 구조를 변경해보았습니다.

src/basic/
├── __tests__/
│   └── origin.test.tsx
├── App.tsx
├── components/
│   ├── Header.tsx
│   ├── index.ts
│   └── Notification.tsx
├── entities/
│   ├── cart/
│   │   ├── components/
│   │   │   └── Cart.tsx
│   │   ├── hooks/
│   │   │   └── useCart.ts
│   │   ├── index.ts
│   │   └── types/
│   │       └── cart.ts
│   ├── coupon/
│   │   ├── components/
│   │   │   ├── AdminCoupon.tsx
│   │   │   ├── AdminCouponCard.tsx
│   │   │   ├── AdminCouponForm.tsx
│   │   │   └── Coupon.tsx
│   │   ├── constants/
│   │   │   └── coupon.ts
│   │   ├── hooks/
│   │   │   ├── useCoupon.ts
│   │   │   └── useCouponForm.ts
│   │   ├── index.ts
│   │   └── types/
│   │       └── coupon.ts
│   ├── index.ts
│   └── product/
│       ├── components/
│       │   ├── AdminProduct.tsx
│       │   ├── AdminProductForm.tsx
│       │   ├── AdminProductTable.tsx
│       │   └── Product.tsx
│       ├── constants/
│       │   └── product.ts
│       ├── hooks/
│       │   ├── useProduct.ts
│       │   ├── useProductForm.ts
│       │   └── useProductSearch.ts
│       ├── index.ts
│       └── types/
│           └── product.ts
├── features/
│   ├── index.ts
│   └── payment/
│       ├── components/
│       │   └── Payment.tsx
│       ├── hooks/
│       │   └── usePayment.ts
│       └── index.ts
├── hooks/
│   ├── index.ts
│   ├── useDebounce.ts
│   ├── useLocalStorage.ts
│   └── useNotification.ts
├── main.tsx
├── pages/
│   ├── AdminPage.tsx
│   ├── index.ts
│   └── MainPage.tsx
└── types/
    ├── index.ts
    └── notification.ts

익숙하지 않은 방법이라 이렇게 쪼개는 방식이 맞는지는 모르겠지만 나름 주로 컴포넌트를 나눌때 ui를 기준으로 나누다 보니 entity로 구분하기는 힘들어보이는 payment의 위치가 애매해졌습니다. 그래서 우선 features로 따로 분리했는데 이것 또한 적절한 방법인지는 여전히 모르겠습니다.

추가적으로 ui / utils / icons 등 과제에서 공통으로 사용될 수 있는 부분은 최상단에서 import 할 수 있도록 했습니다. 폴더 구조를 변경하니 depth가 깊어지면서 상대경로로 파일을 찾는게 너무 길어져서 절대경로로 찾을 수 있도록 처리했습니다.

AS-IS
import { ShoppingBagIcon, XIcon } from '../../../icons';
import { calculateItemTotal } from '../../../utils';
import { Button } from '../../../ui';


TO-BE
import { ShoppingBagIcon, XIcon } from '@/icons';
import { calculateItemTotal } from '@/utils';
import { Button } from '@/ui';

jotai advanced에서 jotai를 적용하면서 props drilling 현상을 제거했습니다.

<div className="min-h-screen bg-gray-50">
    <Notification />
    <Header />

    <main className="max-w-7xl mx-auto px-4 py-8">
      {isAdmin ? (
        <AdminPage />
      ) : (
        <MainPage />
      )}
    </main>
</div>

저는 살면서 이렇게 단촐한 코드를 처음 봤습니다. (거의.. 처음..?)

전역상태관리를 위해 zustand를 사용해보긴했지만 props로 내리는 구조를 주로 사용하고 정말 전역으로 사용하는 경우(ex. theme 관리, mqtt 구독, 공통 커스텀 alert 등)에만 처리했었습니다. 아마 이런 방식으로 적용했으면 이번 과제에서는 notification 영역에만 적용을 하지 않았을까요..? 결론적으로 jotai를 사용함으로써 코드가 깨끗해진게 시각적으로 느껴지니까 기분이 좋았습니다.

과제를 다시 해보면 더 잘 할 수 있었겠다 아쉬운 점이 있다면 무엇인가요?

  • 전체적으로 ‘분리’에만 목적을 두고 코드 나누기를 하다보니 재사용성이나 확장성을 크게 고려하지 못한 부분이 아쉽습니다.
  • form, input, select 부분도 ui 공통으로 분리해서 사용할 수 있었을 것 같습니다.
  • 전체적으로 코드분석을 하지 않고 큰 단위로 분리를 하려고 하면서 느꼈던 불편한 점들에 있어서 아쉬운 부분도 있습니다. 컴포넌트를 분리하다가 어? 얘도 고쳐야겟네? 하면서 삼천포로 빠지고 그리고 다시 또 다른게 보이면 그걸 고치고 있고 그리고 다시 하던걸로 돌아오고. 아무래도 큰 단위로 작업을 하기 시작해서 그랬던 것 같습니다. 작은 단위부터 점진적으로 진행해나갔으면 이런 불편함을 덜 느낄 수 있지 않았을까 싶습니다.

리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문 편하게 남겨주세요 :)

  • 이번 과제에서는 극단적으로 전역상태관리를 안 쓰기 / 쓰기 를 수행해본 것 같은데, props drilling이 불편한 부분이 있음은 분명하지만 모든 상태를 전역으로 관리하는 것이 좋은 방법인가? 에 대한 궁금증이 생겼습니다.
  • 폴더 구조를 두가지 방식으로 시도해보았는데, 어떤 방식이 좀 더 맞는 방향성인지 궁금합니다. 아니면 좀 더 나은 방법이 있었을지? 저는 초기의 구조가 좀 더 가시성이 좋다고 느껴지는데 이게 익숙함의 영향 때문인가 싶습니다.
  • 폴더 구조 두번째에서 위에서 언급한 payment의 위치가 features가 적절했는지? 아니면 애초에 payment라는 단위로 묶었으면 안되었는지 궁금합니다. (entity - features - ui 계층에 대한 확립이 덜 되어 이런 고민을 하게 되는 것 같기도 합니다)

리뷰 받고 싶은 내용이나 궁금한 것에 대한 질문

과제 피드백

수고했어요 효진! "그런데 일단 냅다 분리하고 보니까 이 사단이 나버렸습니다…wow…" 의 결과물을 경험하는 게 취지였으니 ㅋ 잘했습니다. UI가 아닌 컴포넌트를 다루게 되면 필연적으로 데이터를 다루는 값들과 함수들이 복잡해지는 문제가 발생하죠

이번 과제의 경우 사실 폴더구조보다는 얼마나 더 세분하게 나눠보고 필요한 props과 그렇지 않은 props를 구분해서 전역상태관리를 잘 활용해보자는 데 있어습니다. "저는 살면서 이렇게 단촐한 코드를 처음 봤습니다. (거의.. 처음..?)" 라는 것처럼요.

실제 코드를 보면 모든 코드가 그러하진 않않네요. 여전히 컴포넌트에서는 props를 사용하고 있는 컴포넌트들이 많이 보여요. 컴포넌트의 재사용은 중요한 개념이지만 데이터 컴포넌트는 재사용이 거의 안된다는 사실을 기억하면서 무엇은 재사용하면 좋은지 무엇은 클린함을 추구하는게 좋은지 균형을 생각해보길 바래요.

이제 마지막 남은 과제에서는 이 모든 것들의 아쉬움을 한번 제대로 다 적용해볼 수 있을거라고 생각해요. 기대할게요. 화이팅입니다!