항해99 네번째 과제

장운서·2025년 4월 27일

항해99

목록 보기
4/8
post-thumbnail

과제 체크포인트

배포링크

https://unseojang.github.io/front_5th_chapter2-1/

기본과제

  • 코드가 Prettier를 통해 일관된 포맷팅이 적용되어 있는가?
  • 적절한 줄바꿈과 주석을 사용하여 코드의 논리적 단위를 명확히 구분했는가?
  • 변수명과 함수명이 그 역할을 명확히 나타내며, 일관된 네이밍 규칙을 따르는가?
  • 매직 넘버와 문자열을 의미 있는 상수로 추출했는가?
  • 중복 코드를 제거하고 재사용 가능한 형태로 리팩토링했는가?
  • 함수가 단일 책임 원칙을 따르며, 한 가지 작업만 수행하는가?
  • 조건문과 반복문이 간결하고 명확한가? 복잡한 조건을 함수로 추출했는가?
  • 코드의 배치가 의존성과 실행 흐름에 따라 논리적으로 구성되어 있는가?
  • 연관된 코드를 의미 있는 함수나 모듈로 그룹화했는가?
  • ES6+ 문법을 활용하여 코드를 더 간결하고 명확하게 작성했는가?
  • 전역 상태와 부수 효과(side effects)를 최소화했는가?
  • 에러 처리와 예외 상황을 명확히 고려하고 처리했는가?
  • 코드 자체가 자기 문서화되어 있어, 주석 없이도 의도를 파악할 수 있는가?
  • 비즈니스 로직과 UI 로직이 적절히 분리되어 있는가?
  • 코드의 각 부분이 테스트 가능하도록 구조화되어 있는가?
  • 성능 개선을 위해 불필요한 연산이나 렌더링을 제거했는가?
  • 새로운 기능 추가나 변경이 기존 코드에 미치는 영향을 최소화했는가?
  • 코드 리뷰를 통해 다른 개발자들의 피드백을 반영하고 개선했는가?
  • (핵심!) 리팩토링 시 기존 기능을 그대로 유지하면서 점진적으로 개선했는가?

심화과제

  • 변경한 구조와 코드가 기존의 코드보다 가독성이 높고 이해하기 쉬운가?
  • 변경한 구조와 코드가 기존의 코드보다 기능을 수정하거나 확장하기에 용이한가?
  • 변경한 구조와 코드가 기존의 코드보다 테스트를 하기에 더 용이한가?
  • 변경한 구조와 코드가 기존의 모든 기능은 그대로 유지했는가?
  • (핵심!) 변경한 구조와 코드를 새로운 한번에 새로만들지 않고 점진적으로 개선했는가?

과제 셀프회고

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

이번 과제의 핵심은 기존 바닐라 JS 기반의 장바구니 로직을 React 구조로 점진적으로 전환하는 것이었습니다. 단순히 기능만 옮기는 것이 아니라 다음의 기준들을 충족하기 위해 세심하게 리팩토링을 진행했습니다.

✅ 점진적 리팩토링

  • 진행순서를 구성하여 점진적 코드 리펙토링을 진행하였습니다
    /**
  • 진행 순서
    1. 개행을 통한 코드 분리 및 주석 처리
    1. 변수 및 함수 이름 수정
    1. var -> const or let 으로 변경
    1. 상수 만들기
    1. 유틸 함수 최대한 '순수 함수' 형태로 분리
    1. 헬퍼함수 분리
    1. 이벤트 리스너 main() 안으로 넣기
    1. 리액트로 변경이 쉽게 화살표 함수로 변경
    1. 타입스크립트 변경 용의를 위한 가드 추가
    1. 명령형 -> 선언형???
    1. 전역변수 순차적 정리
      */
  • 개인 팀의 컨벤션입니다, 다 지키지는 못한 것 같기도하고...최대한 지키려고 노력하면서 마이그레이션하려고 노력했습니다.
    1. 조건문은 Early return 방식을 사용을 권장한다.

    1. 조건문 반환할 때의 3항 연산자는 1 depth인 경우에만 사용한다.
    2. 인터페이스 앞에 I prefix를 붙인다.
      • ex) IUser, IProduct ,
    3. API 요청 인터페이스는 Request, 응답 인터페이스는 Response 접미사를 사용한다.
    4. 인터페이스나 상태의 이름에 data, info 를 붙이는 것은 최대한 지양한다.
    5. 값을 반환하는 함수는 get으로 시작하는 것을 권장한다.
    6. 리터럴 타입을 strict하게 정의하여 사용한다.
      • type Role = “Admin” | “Guest"
    7. 이벤트 핸들러는 handle prefix를 사용하며 handle + 동사 + 명사 형태로 작성한다.
    8. 복수형 변수명은 해당 단어의 복수형을 사용하기를 권장한다.
      • ex) products, categories, addProducts
      • 복수형으로 쓸 수 없는 명사나 애매한 명사는 공유하여 좋은 이름을 찾는다.
    9. 복수형 컴포넌트명은 List , 단수형은 Item 접미사 사용을 권장한다.
  • 기존 기능을 그대로 유지하면서 구조를 점진적으로 React 컴포넌트화했습니다.

  • main.js의 DOM 조작 기반 구조를 Cart.tsx로 옮기되, 처음에는 로직 중심 컴포넌트로 시작한 뒤 점차 CartItem, ProductSelect, CartSummary,ProductSelect 등 하위 컴포넌트로 쪼갰습니다.

  • 전체 구조를 한 번에 새로 만드는 방식이 아니라, 기존 로직과 맞닿아 있던 계산 로직, 이벤트 핸들링 로직을 하나씩 추출하며 점진적으로 모듈화/컴포넌트화를 했습니다.

  • 컴포넌트 단위로의 역할 분리
    CartItem, CartSummary, ProductSelect 등 의미 있는 단위로 나누고 각각의 책임을 명확히 했습니다. 예를 들어 CartSummary에서는 단순한 출력이 아니라 계산 로직까지 내부에서 useMemo로 처리하도록 해 최적화와 관심사 분리를 동시에 고려했습니다.

  • 상태 관리 일관성 유지
    prodList, cartItems, selectedProductId를 각각의 역할에 맞게 분리하고, 상호작용 시 원본 데이터가 무너지지 않도록 불변성을 최대한 지키려하며 업데이트했습니다. 특히, 재고 감소/복원 추가 로직이 엇갈리지 않도록 updateProductStock, decreaseCartItemQuantity 등의 유틸 함수도 분리했습니다.

  • 기능 단위 유틸 함수화 및 커스텀 훅 분리 실험
    handleAddToCart, handleQuantityChange, useLuckySaleEffect, useSuggestionEffect 등을 직접 작성하며 리액트스러운 구조의 방향성을 고민했습니다. setTimeout 타이머와 관련된 비즈니스 로직도 App 외부가 아닌 커스텀 훅으로 이동시켜 테스트와 유지보수성을 높이기 위해 노력했습니다.

  • 테스트 코드와 호환성 유지
    기존에 존재하던 테스트 스펙과도 최대한 호환되도록 할인 적용, 포인트 계산, 재고 관리의 핵심 비즈니스 로직은 기존 JS 코드에서 React로 마이그레이션 시 정확히 동작하도록 검증했습니다.

  • 이벤트 로직과 화면 구성의 분리

    • 로직이 명확히 파일로 분리되면서, UI 레이아웃과 사용자의 상호작용 처리 코드가 섞이지 않아 각 역할이 뚜렷하게 구분되었습니다.
    • 예: Cart.tsx에서는 오직 컴포넌트의 상태 흐름과 구조만 다루고, 로직은 handlersutils에서 처리함.
    • 예를 들어 handleAddCartClick 함수는 DOM 기반에서도, React 기반에서도 내부 동작만 조금 다르게 전달하면 동일한 비즈니스 로직을 재활용할 수 있도록 설계했습니다.
    • 추후 React에서 Context API나 Redux 등 상태 관리 도구를 쓰더라도 핵심 로직을 그대로 가져와 쓸 수 있도록 추상화되었습니다.
  • 테스트가 쉬운 구조

    • 이벤트 핸들러가 컴포넌트 바깥으로 분리되어 있기 때문에, 실제 DOM 없이도 함수 단위의 유닛 테스트가 최대한 가능하게끔 로직을 구성했습니다.
    • 특히 React Testing Library를 사용할 경우, 이벤트 결과만 검증하면 되고 내부 로직은 따로 테스트할 수 있어 테스트 코드가 단순하고 명확해졌습니다.
  • 관심사의 분리와 협업 효율성 향상

    • 만약 디자이너, 프론트엔드, 백엔드 모두 협업 시 "어떤 이벤트에 어떤 동작이 붙는지"를 빠르게 파악할 수 있는 구조가 되었고, 리팩토링 시에도 이벤트 처리 로직만 집중적으로 교체할 수 있어 변경 범위를 최소화할 수 있습니다.

✅ 함수의 단일 책임 및 재사용성

  • handleAddToCart, handleQuantityChange, handleRemoveToCart 같은 핵심 로직은 하나의 책임만 수행하도록 만들었습니다.
  • 수량 변경, 상품 추가, 삭제와 같은 행위에 대해서도 상태 변경 로직과 UI 이벤트 처리를 분리하여 테스트 가능성과 유지보수성을 고려했습니다.
  • 반복되던 재고 감소/증가 로직은 updateProductStock, decreaseCartItemQuantity 등으로 추출해 재사용성을 높였습니다.

✅ 비즈니스 로직과 UI 로직의 분리

  • CartSummary 컴포넌트는 단순 출력용이 아닌 실제 계산 로직을 내포하도록 구성했습니다. 할인률, 최종 결제 금액, 포인트 계산 로직을 useMemo로 감싸 연산 최적화와 명확한 책임 분리를 동시에 달성했습니다.
  • 이 구조를 통해 비즈니스 로직이 화면에 묻히는 문제를 해결했고, 추후 CartSummary만 따로 테스트하거나 변경하기도 수월해졌습니다.

✅ 상수 추출, 네이밍, 포맷팅

  • 매직넘버(30, 0.1 등)는 모두 constants/discount.ts로 추출하여 최대한 의미 있는 이름을 부여했습니다.
  • 파일/컴포넌트/함수/상수 이름은 역할 기반으로 명확하고 일관되게 작성했으며, Prettier 적용으로 코드 포맷도 통일시켰습니다.
  • 조건문이 복잡했던 로직은 함수화하거나 useMemo 내부에서 가공된 데이터를 중심으로 표현해 가독성을 높였습니다.

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

  • 재고 감소와 장바구니 수량 증가 로직의 불변성 유지 및 순서 보장이 처음에는 완벽하지 않았습니다. setState 비동기 동작에 따른 혼란을 방지하기 위해 prev => next 형태로 모두 수정했지만, 초기에 의도한 대로 동작하지 않아 디버깅 시간이 길어졌습니다.
  • 커스텀 훅(useLuckySaleEffect, useSuggestionEffect)으로 번개 세일/추천 상품 로직을 분리한 것은 좋았지만, 의존성 배열이나 타이머 중첩 관련해서 조금 더 최적화할 여지가 있었습니다.

리뷰 받고 싶은 내용이나 궁금한 점

  • CartSummary에서의 계산 로직을 useMemo로 감싸는 방식이 성능 측면에서는 명확하지만, 이 정도 복잡도의 계산도 분리하는 것이 일반적인지, 혹은 더 좋은 추상화 방법이 있을지 궁금합니다.
  • 현재는 상태 관리를 useState 기반으로만 하고 있는데, 상품 목록(prodList)와 장바구니(cartItems)의 상태 동기화가 필요한 경우에는 어떤 상태 관리 전략(Context, Zustand 등)이 적절할지 피드백 받고 싶습니다.
  • 조건부 렌더링(재고 부족, 품절) 로직을 JSX에서 다루다보니 컴포넌트 내 조건이 점점 복잡해졌습니다. 이럴 때 보통 presenter 컴포넌트 등을 분리해서 다루는 게 일반적인지 궁금합니다.
  • 코드 분리를 최대한 한다고 했는데 아직 부족한 부분이 더 있는 것 같습니다. 다른 사람의 코드를 보면서 좋은 코드를 찾을 순 있었는데 어떻게 하면 더 보기 좋고 구성있게 분리할수 있을까 라는 생각을 지울 순 없었습니다. 어떻게 하면 더 좋은 코드로 분리 시킬수 있는지 방법을 알고싶습니다.
  • 리액트를 다루는데 아직 익숙하지 않은 것 같습니다. 지금 상태보다 더 효율성을 추구할수 있었을까요??

체크리스트 기반 정리

  • 코드 포맷은 Prettier로 통일했고, ESLint도 v9 방식으로 설정을 마이그레이션 완료했습니다.
  • 불필요한 반복문, 조건문은 함수로 추출했고, CartItem 내부의 수량 변경도 onChangeQuantity props로 처리해 SRP를 지켰습니다.
  • 중복된 DOM 조작 로직은 없으며, 모든 로직은 상태 기반으로 처리됩니다.
  • 기능 추가 시 영향이 최소화되도록 Cart.tsx는 상태 중심 컴포넌트로, 비즈니스 계산은 CartSummary, UI 제어는 CartItem/ProductSelect로 위임하는 구조입니다.
  • 기존 JS 코드를 모두 React로 변환한 것이 아니라, 동작 로직을 유지하면서도 점진적으로 컴포넌트화하고 비즈니스 분리하는 데 집중했습니다.

✨ 구조 개선, 재사용성, 테스트 가능성, 가독성 모든 관점에서 개선을 목표로 했으며, 그 과정에서 리액트의 패턴과 상태 관리에 대한 실전 감각을 익힐 수 있었던 유익한 과제였습니다.

profile
성공을 위해선 과정만 있을 뿐이다

0개의 댓글