Skip to content

Conversation

@naringst
Copy link

@naringst naringst commented Nov 30, 2025

과제 배포 링크

과제의 핵심취지

  • 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는 분리되어 있나요?

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

심화과제

  • 이번 심화과제는 Context나 Jotai를 사용해서 Props drilling을 없애는 것입니다.

  • 어떤 props는 남겨야 하는지, 어떤 props는 제거해야 하는지에 대한 기준을 세워보세요.

  • Context나 Jotai를 사용하여 상태를 관리하는 방법을 익히고, 이를 통해 컴포넌트 간의 데이터 전달을 효율적으로 처리할 수 있습니다.

  • Context나 Jotai를 사용해서 전역상태관리를 구축했나요?

  • 전역상태관리를 통해 domain custom hook을 적절하게 리팩토링 했나요?

  • 도메인 컴포넌트에 도메인 props는 남기고 props drilling을 유발하는 불필요한 props는 잘 제거했나요?

  • 전체적으로 분리와 재조립이 더 수월해진 결합도가 낮아진 코드가 되었나요?

과제 셀프회고

과제를 하면서 내가 알게된 점, 좋았던 점은 무엇인가요?

[디자인 패턴들]

  • 컴포넌트 합성

    • 작은 단위의 UI 컴포넌트들(Button, DeleteButton, CartQuantityHandler, CartItemPrice 등)을 조합하여 더 큰 컴포넌트를 만드는 패턴을 경험했습니다. 이를 통해 각 컴포넌트의 재사용성과 테스트 용이성이 크게 향상되었습니다.
    • CartItemComponent에서 여러 작은 컴포넌트들을 합성하여 복잡한 UI를 구성하는 과정에서, 각 컴포넌트가 단일 책임을 가지도록 설계하는 것이 얼마나 중요한지 깨달았습니다.
  • 변경에 유연한 코드

    • 엔티티 컴포넌트와 UI 컴포넌트를 분리함으로써, 비즈니스 로직 변경 시 UI 컴포넌트는 그대로 유지할 수 있고, UI 변경 시에도 비즈니스 로직에 영향을 주지 않을 수 있다는 점을 경험했습니다.
    • Service 레이어(cart.service.ts)에 순수함수들을 분리함으로써, 계산 로직을 독립적으로 테스트하고 재사용할 수 있게 되었습니다. 예를 들어 calculateItemTotal, calculateCartTotalPrice 같은 함수들은 어디서든 동일한 결과를 보장하는 순수함수로 작성되어 있어, 코드의 예측 가능성이 높아졌습니다.
  • Container-Presenter 패턴의 실전 적용

    • basic 버전에서 Container 컴포넌트가 상태와 비즈니스 로직을 관리하고, Presenter 컴포넌트는 UI 렌더링만 담당하는 패턴을 적용했습니다. 이를 통해 관심사의 분리가 명확해졌고, 컴포넌트의 책임이 분명해졌습니다.
  • 화면과 코드의 1:1 매칭

    • 화면의 구조와 코드의 구조가 직관적으로 매칭되도록 노력했습니다. 예를 들어, CartSection, CartTitle, CartItem 같은 컴포넌트 이름이 실제 화면의 구조를 그대로 반영하도록 했습니다. 이를 통해 코드를 읽는 사람이 화면을 보지 않아도 어떤 부분을 담당하는지 쉽게 이해할 수 있도록 했습니다.

이번 과제에서 내가 제일 신경 쓴 부분은 무엇인가요?

[함수형 프로그래밍]

  • 순수함수 분리와 부수효과 최소화
    • cart.service.ts에서 모든 계산 로직을 순수함수로 분리했습니다. calculateItemTotal, getMaxApplicableDiscount, calculateCartTotalPrice 같은 함수들은 입력값에 대해서만 결과를 반환하고, 외부 상태를 변경하지 않도록 작성했습니다.
    • Custom Hook(useCart, useCoupon, useProduct)에서는 상태 관리와 부수효과(side effect)를 처리하고, 순수한 계산 로직은 Service 레이어로 분리하여 함수형 프로그래밍의 원칙을 지키려고 노력했습니다.
    • 순수 util 함수와 비즈니스 로직, effect(액션)이 섞여 있는 로직들이 많았는데, 이것들을 분리해서 함수의 역할을 하나로 가져가려고 노력했습니다. 예를 들어, calculateItemTotal은 순수 계산 함수로, useCartaddToCart는 상태 변경과 부수효과를 처리하는 액션으로 명확히 구분했습니다. (시간이 없어서 다 분리는 못했지만, 이 방향으로 계속 개선해나가고 싶습니다!)

[관심사의 분리 - 응집도 높이고 결합도 낮추기]

  • 엔티티 기반 계층 구조 설계

    • 엔티티를 다루는 상태(cart, products, coupons)와 그렇지 않은 상태(isAdmin, isShowPopup)를 명확히 구분했습니다.
    • 엔티티를 다루는 컴포넌트(CartItemComponent, ProductItem)와 UI 컴포넌트(Button, Input, Select)를 분리하여, 각각의 책임을 명확히 했습니다.
    • features 폴더 구조를 통해 cart, product, coupon, notification 등 각 도메인별로 관련된 컴포넌트, 훅, 서비스를 응집도 높게 구성했습니다.
  • Custom Hook을 통한 로직 캡슐화

    • useCart, useCoupon, useProduct 등의 Custom Hook을 통해 각 도메인의 상태 관리와 비즈니스 로직을 캡슐화했습니다. 이를 통해 컴포넌트는 UI 렌더링에만 집중할 수 있게 되었습니다.
  • 데이터, 로직, UI의 명확한 구분

    • 프론트엔드 코드는 전체적으로 데이터 + 로직 + UI로 구별되기 때문에, 해당 내용들을 각각 구별하려고 노력했습니다.
    • 데이터: atoms, types, 초기 상태값
    • 로직: service 레이어의 순수함수, Custom Hook의 비즈니스 로직
    • UI: 컴포넌트의 렌더링 로직
    • 이렇게 구분함으로써 각 부분의 책임이 명확해지고, 테스트와 유지보수가 쉬워졌습니다.
  • 코드 분리 시점과 기준

    • "어디서부터 코드를 분리해야 할까?"라는 고민을 많이 했습니다. 너무 일찍 분리하면 오버엔지니어링이 되고, 너무 늦게 분리하면 리팩토링이 어려워집니다.
    • 코드 냄새가 나는 부분을 위주로 분리하려고 했습니다. 예를 들어:
      • 함수가 너무 길어지거나 여러 책임을 가질 때
      • 같은 로직이 여러 곳에서 반복될 때
      • 테스트하기 어려운 구조일 때
      • 컴포넌트가 비즈니스 로직과 UI 로직을 모두 포함할 때
    • 각 컴포넌트의 책임이 어디까지일까를 고민하면서, 단일 책임 원칙을 지키려고 노력했습니다.
  • 인터페이스의 직관성

    • 인터페이스를 최대한 직관적으로 가져가려고 노력했습니다. 함수명, 변수명, 컴포넌트명이 그 역할을 명확히 드러내도록 했습니다.
    • 예를 들어, calculateItemTotal은 "아이템의 총액을 계산한다"는 의미가 바로 드러나고, useCart는 "장바구니 관련 로직을 다룬다"는 것을 명확히 합니다.

[전역 상태를 써서 props drilling을 막으면 과연 무조건 좋을까?]

  • 언제 전역 상태를 써야 할까?
    • basic 버전에서는 props drilling이 발생했습니다. AppShopPageCartCartItem 같은 깊은 prop 전달이 필요했고, 이는 코드의 가독성을 떨어뜨렸습니다.
    • advanced 버전에서는 Jotai를 사용하여 전역 상태를 관리했습니다. cartAtom, isAdminAtom 등을 통해 여러 컴포넌트에서 공유되는 상태를 효율적으로 관리할 수 있었습니다.
    • 하지만 모든 상태를 전역으로 관리하는 것은 오히려 복잡도를 증가시킬 수 있습니다. 예를 들어, 특정 컴포넌트 내에서만 사용되는 로컬 상태(isShowPopup 같은 UI 상태)는 전역 상태로 만들 필요가 없습니다.
    • **"어디까지 전역 상태로 관리할까?"**를 고민을 많이 했습니다.
      • products는 전역 상태로 관리해야 할까?
        • products는 여러 컴포넌트(상품 목록, 장바구니, 관리자 페이지)에서 사용되지만, 읽기 전용 데이터에 가깝습니다.
        • 전역 상태로 관리하면 어디서든 접근 가능하지만, 불필요한 리렌더링을 유발할 수 있습니다.
        • 결국 전역 상태로 관리하기로 결정했지만, 이는 프로젝트의 규모와 요구사항에 따라 달라질 수 있다는 점을 깨달았습니다.
    • 전역 상태를 사용해야 하는 경우:
      • 여러 컴포넌트에서 공유되어야 하는 엔티티 데이터 (cart, products, coupons)
      • 앱 전반에 걸쳐 사용되는 설정값 (isAdmin)
    • 로컬 상태를 유지해야 하는 경우:
      • 특정 컴포넌트 내에서만 사용되는 UI 상태 (모달 열림/닫힘, 폼 입력값 등)
      • 부모-자식 관계가 명확하고 깊이가 얕은 경우

이번 과제를 통해 앞으로 해보고 싶은게 있다면 알려주세요!

  • 여기서 요구사항이 추가되었을 때, 과연 내 코드가 새 기획에 용이한 코드인지 확인해보기
    • 예를 들어, "장바구니에 상품을 담을 때 알림을 표시하는 기능"이 추가된다면, 현재 구조에서 useNotification hook을 useCart에 통합하는 것이 좋을지, 아니면 별도의 이벤트 시스템을 구축하는 것이 좋을지 고민해보고 싶습니다.
    • "상품에 리뷰 기능 추가", "할인 정책 변경", "결제 수단 추가" 같은 요구사항이 들어왔을 때, 현재의 계층 구조와 컴포넌트 분리가 얼마나 확장 가능한지 테스트해보고 싶습니다.

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

  • Jotai의 derived atom을 사용할 때와 일반 atom을 사용할 때의 성능 차이와 사용 시점에 대한 조언이 필요합니다. totalCartItemCountAtom 같은 derived atom이 매번 계산되는데, 이게 최적의 방법인지 궁금합니다.
  • Service 레이어의 순수함수들이 많아지면서, 함수 간의 의존성 관리가 복잡해질 수 있습니다. 예를 들어 calculateItemTotalgetMaxApplicableDiscount를 사용하는데, 이런 의존성을 어떻게 관리하는 것이 좋을까요?
  • 엔티티 컴포넌트와 UI 컴포넌트의 경계를 정하는 기준이 때로는 모호합니다. 예를 들어 CartItemComponent는 엔티티 컴포넌트인데, 내부에 Button, DeleteButton 같은 UI 컴포넌트를 사용하고 있습니다. 이런 구조가 적절한지, 아니면 더 분리할 수 있는지 궁금합니다.
  • 훅 간 의존성을 갖게 될 때에는 어떻게 해야 할까요?
    • 예를 들어, useCartuseProduct의 데이터를 필요로 하거나, useCouponuseCart의 상태를 참조해야 할 때, 이런 의존성을 어떻게 관리하는 것이 좋을까요?
    • 훅을 합치는 것과 의존성을 주입하는 것 중 어떤 방식이 더 나은지 궁금합니다.

"이건 이렇게 해도 될까?", "이 방식이 과연 좋은 방식일까?" 하고 망설였던 선택들

  • 전역 상태 관리 범위

    • products를 전역 상태로 관리할지, props로 전달할지 고민이 많았습니다. 결국 전역 상태로 관리하기로 했지만, 이게 정말 최선인지는 계속 의문이 듭니다.
    • isAdmin 같은 UI 상태도 전역으로 관리했는데, 이게 과한 건 아닐까요?
  • 컴포넌트 분리 수준

    • CartItemComponent 내부에 DeleteButton, CartQuantityHandler, CartItemPrice 같은 작은 컴포넌트들을 만들었는데, 이 정도로 세분화하는 것이 과한 건 아닐까요?
    • 하지만 각 컴포넌트가 단일 책임을 가지게 되어 테스트와 재사용이 쉬워진다는 장점이 있어서 이 방향으로 진행했습니다.
  • Service 레이어의 함수 분리

    • getMaxApplicableDiscount를 private 함수로 두고 calculateItemTotal에서만 사용하도록 했는데, 이게 맞는 선택인지 고민이 됩니다.
    • 테스트를 위해서는 export하는 것이 좋지만, 캡슐화를 위해서는 private이 좋은데, 이 균형을 어떻게 맞춰야 할까요?

처음엔 이렇게 하려다가 나중에 전혀 다른 방향으로 바꾼 것들

  • Product 관련 Hook 분리 방식의 변화
    • 처음에는 useProducts라는 하나의 큰 Hook으로 상품 목록 관리와 폼 관리를 모두 처리하려고 했습니다.
    • 하지만 나중에 form과 관련된 상태는 useProductForm으로 분리해서 각각의 책임으로 분리했습니다.
    • 이렇게 분리하니:
      • useProducts는 상품 목록의 CRUD 작업에만 집중
      • useProductForm은 폼 상태 관리와 유효성 검사에만 집중
      • 각 Hook의 책임이 명확해지고, 테스트와 재사용이 쉬워졌습니다.
    • 이 경험을 통해 "하나의 Hook이 너무 많은 책임을 가지면 분리해야 한다"는 기준을 세울 수 있었습니다.

배포 이슈 및 해결 과정

문제 상황

GitHub Pages에 배포할 때, 빌드는 성공했지만 실제 배포된 사이트에서 리소스(JS, CSS 파일)를 찾지 못하는 문제가 발생했습니다. 브라우저 콘솔에서 404 에러가 발생하며 애플리케이션이 제대로 로드되지 않았습니다.

원인 분석

  • GitHub Pages는 리포지토리 이름을 base path로 사용합니다. 예를 들어 https://username.github.io/front_7th_chapter3-2/ 같은 형태로 배포됩니다.
  • Vite는 기본적으로 root path(/)를 기준으로 리소스를 로드하는데, 서브 경로에 배포할 경우 상대 경로 문제가 발생합니다.
  • vite.config.ts에서 base 옵션을 설정하지 않으면, 빌드된 HTML 파일에서 절대 경로(/assets/...)로 리소스를 참조하게 되어 GitHub Pages의 서브 경로에서 제대로 작동하지 않습니다.

해결 방법

vite.config.ts 파일에 base 옵션을 추가하여 GitHub Pages의 서브 경로를 명시했습니다:

const base: string = '/front_7th_chapter3-2/';

export default mergeConfig(
  defineConfig({
    base, // 이 설정이 중요!
    plugins: [react()],
    // ...
  }),
  // ...
);

배운 점

  • Vite의 base 옵션은 빌드 시 모든 asset 경로에 prefix를 추가합니다. 이를 통해 서브 디렉토리에 배포할 때도 올바른 경로로 리소스를 참조할 수 있습니다.
  • GitHub Actions의 배포 워크플로우(.github/workflows/deploy.yml)에서 빌드 단계와 배포 단계가 분리되어 있어, 빌드 산출물(dist 폴더)이 올바르게 생성되는지 확인하는 것이 중요합니다.
  • 배포 환경에 따라 base 경로를 동적으로 설정할 수 있도록 환경 변수를 활용하는 방법도 고려해볼 수 있습니다 (예: 개발 환경은 /, 프로덕션은 /front_7th_chapter3-2/).

@naringst naringst changed the title 과제 시작 [2팀 정나리] Chapter3-2. 디자인 패턴과 함수형 프로그래밍 그리고 상태 관리 설계 #8 Nov 30, 2025
@naringst naringst changed the title [2팀 정나리] Chapter3-2. 디자인 패턴과 함수형 프로그래밍 그리고 상태 관리 설계 #8 [2팀 정나리] Chapter3-2. 디자인 패턴과 함수형 프로그래밍 그리고 상태 관리 설계 Nov 30, 2025
- 쿠폰 사용 가능 체크 함수
-  실제 쿠폰 적용 로직
으로 분리
@naringst naringst force-pushed the main branch 3 times, most recently from 745ae5e to 8f7436e Compare December 5, 2025 17:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant