Skip to content

Conversation

@1lmean
Copy link

@1lmean 1lmean commented Nov 10, 2025

과제 체크포인트

배포 링크

https://1lmean.github.io/front_7th_chapter2-1/

기본과제

상품목록

상품 목록 로딩

  • 페이지 접속 시 로딩 상태가 표시된다
  • 데이터 로드 완료 후 상품 목록이 렌더링된다
  • 로딩 실패 시 에러 상태가 표시된다
  • 에러 발생 시 재시도 버튼이 제공된다

상품 목록 조회

  • 각 상품의 기본 정보(이미지, 상품명, 가격)가 카드 형태로 표시된다

한 페이지에 보여질 상품 수 선택

  • 드롭다운에서 10, 20, 50, 100개 중 선택할 수 있으며 기본 값은 20개 이다.
  • 선택 변경 시 즉시 목록에 반영된다

상품 정렬 기능

  • 상품을 가격순/이름순으로 오름차순/내림차순 정렬을 할 수 있다.
  • 드롭다운을 통해 정렬 기준을 선택할 수 있다
  • 정렬 변경 시 즉시 목록에 반영된다

무한 스크롤 페이지네이션

  • 페이지 하단 근처 도달 시 다음 페이지 데이터가 자동 로드된다
  • 스크롤에 따라 계속해서 새로운 상품들이 목록에 추가된다
  • 새 데이터 로드 중일 때 로딩 인디케이터와 스켈레톤 UI가 표시된다
  • 홈 페이지에서만 무한 스크롤이 활성화된다

상품을 장바구니에 담기

  • 각 상품에 장바구니 추가 버튼이 있다
  • 버튼 클릭 시 해당 상품이 장바구니에 추가된다
  • 추가 완료 시 사용자에게 알림이 표시된다

상품 검색

  • 상품명 기반 검색을 위한 텍스트 입력 필드가 있다
  • 검색 버튼 클릭으로 검색이 수행된다
  • Enter 키로 검색이 수행된다
  • 검색어와 일치하는 상품들만 목록에 표시된다

카테고리 선택

  • 사용 가능한 카테고리들을 선택할 수 있는 UI가 제공된다
  • 선택된 카테고리에 해당하는 상품들만 표시된다
  • 전체 상품 보기로 돌아갈 수 있다
  • 2단계 카테고리 구조를 지원한다 (1depth, 2depth)

카테고리 네비게이션

  • 현재 선택된 카테고리 경로가 브레드크럼으로 표시된다
  • 브레드크럼의 각 단계를 클릭하여 상위 카테고리로 이동할 수 있다
  • "전체" > "1depth 카테고리" > "2depth 카테고리" 형태로 표시된다

현재 상품 수 표시

  • 현재 조건에서 조회된 총 상품 수가 화면에 표시된다
  • 검색이나 필터 적용 시 상품 수가 실시간으로 업데이트된다

장바구니

장바구니 모달

  • 장바구니 아이콘 클릭 시 모달 형태로 장바구니가 열린다
  • X 버튼이나 배경 클릭으로 모달을 닫을 수 있다
  • ESC 키로 모달을 닫을 수 있다
  • 모달에서 장바구니의 모든 기능을 사용할 수 있다

장바구니 수량 조절

  • 각 장바구니 상품의 수량을 증가할 수 있다
  • 각 장바구니 상품의 수량을 감소할 수 있다
  • 수량 변경 시 총 금액이 실시간으로 업데이트된다

장바구니 삭제

  • 각 상품에 삭제 버튼이 배치되어 있다
  • 삭제 버튼 클릭 시 해당 상품이 장바구니에서 제거된다

장바구니 선택 삭제

  • 각 상품에 선택을 위한 체크박스가 제공된다
  • 선택 삭제 버튼이 있다
  • 체크된 상품들만 일괄 삭제된다

장바구니 전체 선택

  • 모든 상품을 한 번에 선택할 수 있는 마스터 체크박스가 있다
  • 전체 선택 시 모든 상품의 체크박스가 선택된다
  • 전체 해제 시 모든 상품의 체크박스가 해제된다

장바구니 비우기

  • 장바구니에 있는 모든 상품을 한 번에 삭제할 수 있다

상품 상세

상품 클릭시 상세 페이지 이동

  • 상품 목록에서 상품 이미지나 상품 정보 클릭 시 상세 페이지로 이동한다
  • URL이 /product/{productId} 형태로 변경된다
  • 상품의 자세한 정보가 전용 페이지에서 표시된다

상품 상세 페이지 기능

  • 상품 이미지, 설명, 가격 등의 상세 정보가 표시된다
  • 전체 화면을 활용한 상세 정보 레이아웃이 제공된다

상품 상세 - 장바구니 담기

  • 상품 상세 페이지에서 해당 상품을 장바구니에 추가할 수 있다
  • 페이지 내에서 수량을 선택하여 장바구니에 추가할 수 있다
  • 수량 증가/감소 버튼이 제공된다

관련 상품 기능

  • 상품 상세 페이지에서 관련 상품들이 표시된다
  • 같은 카테고리(category2)의 다른 상품들이 관련 상품으로 표시된다
  • 관련 상품 클릭 시 해당 상품의 상세 페이지로 이동한다
  • 현재 보고 있는 상품은 관련 상품에서 제외된다

상품 상세 페이지 내 네비게이션

  • 상품 상세에서 상품 목록으로 돌아가는 버튼이 제공된다
  • 브레드크럼을 통해 카테고리별 상품 목록으로 이동할 수 있다
  • SPA 방식으로 페이지 간 이동이 부드럽게 처리된다

사용자 피드백 시스템

토스트 메시지

  • 장바구니 추가 시 성공 메시지가 토스트로 표시된다
  • 장바구니 삭제, 선택 삭제, 전체 삭제 시 알림 메시지가 표시된다
  • 토스트는 3초 후 자동으로 사라진다
  • 토스트에 닫기 버튼이 제공된다
  • 토스트 타입별로 다른 스타일이 적용된다 (success, info, error)

심화과제

SPA 네비게이션 및 URL 관리

페이지 이동

  • 어플리케이션 내의 모든 페이지 이동(뒤로가기/앞으로가기를 포함)은 하여 새로고침이 발생하지 않아야 한다.

상품 목록 - URL 쿼리 반영

  • 검색어가 URL 쿼리 파라미터에 저장된다
  • 카테고리 선택이 URL 쿼리 파라미터에 저장된다
  • 상품 옵션이 URL 쿼리 파라미터에 저장된다
  • 정렬 조건이 URL 쿼리 파라미터에 저장된다
  • 조건 변경 시 URL이 자동으로 업데이트된다
  • URL을 통해 현재 검색/필터 상태를 공유할 수 있다

상품 목록 - 새로고침 시 상태 유지

  • 새로고침 후 URL 쿼리에서 검색어가 복원된다
  • 새로고침 후 URL 쿼리에서 카테고리가 복원된다
  • 새로고침 후 URL 쿼리에서 옵션 설정이 복원된다
  • 새로고침 후 URL 쿼리에서 정렬 조건이 복원된다
  • 복원된 조건에 맞는 상품 데이터가 다시 로드된다

장바구니 - 새로고침 시 데이터 유지

  • 장바구니 내용이 브라우저에 저장된다
  • 새로고침 후에도 이전 장바구니 내용이 유지된다
  • 장바구니의 선택 상태도 함께 유지된다

상품 상세 - URL에 ID 반영

  • 상품 상세 페이지 이동 시 상품 ID가 URL 경로에 포함된다 (/product/{productId})
  • URL로 직접 접근 시 해당 상품의 상세 페이지가 자동으로 로드된다

상품 상세 - 새로고침시 유지

  • 새로고침 후에도 URL의 상품 ID를 읽어서 해당 상품 상세 페이지가 유지된다

404 페이지

  • 존재하지 않는 경로 접근 시 404 에러 페이지가 표시된다
  • 홈으로 돌아가기 버튼이 제공된다

AI로 한 번 더 구현하기

  • 기존에 구현한 기능을 AI로 다시 구현한다.
  • 이 과정에서 직접 가공하는 것은 최대한 지양한다.

과제 셀프회고

과제 의도와는 다르게, AI를 너무나 적극 활용한 것 같아 아쉽습니다. 주제가 주제이니만큼 심도 깊게 공부해가며 과제를 하고 싶었으나 결국에는 시간이 모자라 ... 완료하지 못하였습니다.

기술적 성장

  • 커스텀 SPA 라우터 구현: React Router나 Vue Router 같은 라이브러리 없이 순수 JavaScript로 SPA 라우터를 직접 구현했습니다. History API와 URL 파싱, 경로 매칭 로직을 구현하면서 라우팅의 동작 원리를 깊이 이해할 수 있었습니다.

  • 옵저버 패턴을 활용한 상태 관리: 전역 상태 관리를 위한 간단한 스토어를 구현했습니다. 옵저버 패턴을 사용하여 상태 변경 시 구독자들에게 자동으로 알림을 보내는 메커니즘을 구현했습니다. 이 과정에서 React의 상태 관리 개념과 유사한 패턴을 구현하려고 했는데, 잘 된건지는 모르겠습니다.

  • IntersectionObserver API: 무한 스크롤을 구현하면서 IntersectionObserver API를 처음 사용해봤습니다. 기존의 scroll 이벤트 리스너 대신 더 효율적인 방법으로 뷰포트 교차를 감지할 수 있다는 것을 배웠습니다.

  • URL 쿼리 파라미터와 상태 동기화: 검색어, 카테고리, 정렬 조건 등을 URL 쿼리 파라미터로 관리하면서, 새로고침 후에도 상태를 복원할 수 있도록 구현했습니다. 이 과정에서 URLSearchParams API를 활용하는 방법을 학습했습니다.

  • 이벤트 기반 아키텍처: EventBus를 구현하여 컴포넌트 간 느슨한 결합을 유지했습니다. 이벤트 기반 통신을 통해 라우터, 상태 관리, UI 컴포넌트 간의 의존성을 줄일 수 있었습니다.

  • Enhancer 패턴: 라우터의 기능을 확장하기 위해 enhancer 패턴을 사용했습니다. 라우터에 새로운 기능을 추가할 때 기존 코드를 수정하지 않고도 확장할 수 있는 구조를 만들어보았습니다.

자랑하고 싶은 코드

  • appStore.js의 상태 관리 구조: 단순하면서도 확장 가능한 상태 관리 구조를 만들었습니다. updateHomepage, updateCart 함수를 통해 상태 업데이트를 일관되게 처리하고, 옵저버 패턴을 통해 자동으로 UI를 업데이트하도록 구현했습니다.

  • URL 상태 동기화: 검색, 필터, 정렬 등의 상태를 URL 쿼리 파라미터로 관리하여 브라우저의 뒤로가기/앞으로가기와 새로고침 시에도 상태가 유지되도록 구현했습니다.

개선이 필요하다고 생각하는 코드

  • 이벤트 리스너 정리: homepageEvents.jsCartModal.js에서 이벤트 리스너를 등록할 때, cleanup 함수를 제대로 호출하지 않는 부분이 있습니다. 메모리 누수를 방지하기 위해 이벤트 리스너 정리 로직을 더 체계적으로 관리해야 합니다.

  • 에러 처리: API 호출 실패 시 에러 처리가 일관되지 않습니다. 에러 타입에 따라 다른 처리를 하거나, 전역 에러 핸들러를 추가하는 것이 좋을 것 같습니다.

  • 타입 안정성: TypeScript를 사용하지 않아 타입 에러를 런타임에 발견하게 됩니다. 객체의 속성에 접근할 때 옵셔널 체이닝을 많이 사용하지만, 더 명확한 타입 정의가 필요합니다.

  • 컴포넌트 구조: 일부 컴포넌트에서 HTML 문자열을 직접 생성하는 부분이 많은데, 이를 더 모듈화하고 재사용 가능한 구조로 개선할 수 있을 것 같습니다.

학습 효과 분석

  • 가장 큰 배움이 있었던 부분: SPA 라우터를 직접 구현하면서 라우팅의 동작 원리를 깊이 이해할 수 있었습니다. 또한 URL과 상태를 동기화하는 방법을 학습하면서, 사용자 경험을 개선하는 방법을 배울 수 있었습니다.

  • 추가 학습이 필요한 영역:

    • 테스트 코드 작성 방법 (단위 테스트, 통합 테스트)
    • 성능 최적화 (가상 스크롤, 코드 스플리팅)
    • 접근성 (ARIA 속성, 키보드 네비게이션)
    • 상태 관리 라이브러리 (Redux, Zustand 등)의 내부 동작 원리
    • 배포 ...
  • 실무 적용 가능성: 이번 과제에서 구현한 패턴들은 실무에서도 적용 가능합니다. 특히 커스텀 라우터 구현 경험은 프레임워크 없이 순수 JavaScript로 웹 애플리케이션을 구축할 때 유용할 것입니다. 또한 옵저버 패턴을 활용한 상태 관리는 작은 규모의 프로젝트에서 상태 관리 라이브러리 없이도 충분히 사용할 수 있습니다.

과제 피드백

  • 과제에서 모호하거나 애매했던 부분:

    • "새로고침 시 상태 유지" 요구사항에서, 장바구니의 선택 상태(체크박스)도 함께 유지해야 하는지 명확하지 않았습니다. 현재는 장바구니 아이템만 유지하고 선택 상태는 유지하지 않도록 구현했습니다.
    • 404 페이지 구현이 심화 과제에 포함되어 있지만, 라우터에서 이미 notFound 핸들러를 제공하고 있어 구현이 간단했습니다. 다만 체크박스가 비어있어 아직 구현하지 않은 상태입니다.
  • 과제에서 좋았던 부분:

    • URL 상태 동기화와 같은 실무에서 자주 사용하는 패턴을 학습할 수 있어 유용했습니다.
    • E2E 테스트를 통해 구현한 기능이 제대로 동작하는지 확인할 수 있어 좋았습니다.

AI 활용 경험 공유하기

  • 사용한 AI 도구: Cursor

  • 프롬프트를 작성한 과정:

    • 구체적인 요구사항을 명확히 기술했습니다 (예: "IntersectionObserver를 사용하여 무한 스크롤을 구현해줘")
    • 기존 코드 구조를 함께 제공하여 일관성 있는 코드를 생성받을 수 있도록 했습니다
    • 에러가 발생했을 때는 에러 메시지와 관련 코드를 함께 제공하여 정확한 해결책을 얻을 수 있도록 했습니다
  • AI가 일을 더 잘 하게 만든 방법:

    • 복잡한 로직을 구현할 때 AI에게 먼저 구현을 요청한 후, 생성된 코드를 검토하고 필요한 부분을 수정했습니다
    • 반복적인 코드 작성(예: 이벤트 리스너 바인딩)을 AI에게 요청하여 생산성을 높였습니다
    • 에러 디버깅 시 AI에게 에러 메시지와 컨텍스트를 제공하여 빠르게 문제를 해결할 수 있었습니다
  • 내가 작성한 코드와 비교하기:

    • 프로젝트의 전체 구조를 고려하지 않고 생성하는 경우가 있어, 생성된 코드를 프로젝트 구조에 맞게 수정해야 했습니다

리뷰 받고 싶은 내용

1. CartModal.js의 이벤트 리스너 관리

setupCartModal() 함수에서 동적으로 생성되는 요소들에 대해 이벤트 리스너를 재바인딩하고 있습니다. removeEventListener를 호출한 후 addEventListener를 호출하는 패턴을 사용했습니다.

  • 현재 구현: ensureRendered() 함수 내에서 매번 기존 리스너를 제거하고 새로 등록합니다.
  • 고민 사항: 이 방식이 메모리 누수를 방지하는 올바른 방법인지, 그리고 더 효율적인 방법(예: 이벤트 위임)이 있는지 궁금합니다. 또한 isSetup 플래그를 사용하여 한 번만 설정되도록 했는데, 이 패턴이 적절한지 확인하고 싶습니다.

2. 이벤트 리스너 cleanup 로직

여러 컴포넌트에서 이벤트 리스너를 등록하지만, 명시적인 cleanup 함수를 반환하지 않는 경우가 있습니다.

  • 현재 구현: setupCartModal, setupHeader 등에서 라우터를 구독하지만, cleanup 함수를 반환하지 않습니다.
  • 고민 사항: 애플리케이션이 SPA이고 페이지를 벗어나지 않는 구조라면 cleanup이 필수인지, 그리고 cleanup이 필요한 경우 어떤 패턴으로 구현하는 것이 좋은지 조언해 주실 수 있을까요?

3. 디렉토리 구조와 모듈 분리

현재 프로젝트 구조는 다음과 같습니다:

src/
  - api/          # API 호출 로직
  - components/   # UI 컴포넌트
  - events/       # 이벤트 핸들러
  - mocks/        # MSW 모킹
  - pages/        # 페이지 컴포넌트
  - router/       # 라우터 및 enhancer
  - store/        # 상태 관리
  - utils/         # 유틸리티
  • 현재 구조: 기능별로 디렉토리를 분리했고, components, pages, events 등으로 구분했습니다.
  • 고민 사항: 협업 경험이 부족하고 코드 리뷰 경험이 없어서, 이런 디렉토리 구조가 실무에서 일반적으로 사용되는 패턴인지, 그리고 더 나은 구조가 있는지 궁금합니다. 특히 events/ 폴더에 이벤트 핸들러를 모아둔 것이 적절한지, 아니면 각 컴포넌트나 페이지와 함께 두는 것이 나은지 조언해 주실 수 있을까요? 또한 router/enhancers/ 같은 중첩 구조가 적절한지도 확인하고 싶습니다. 사실 components/같은 경우에도 현업에서는 페이지 별로 디렉토리를 나눴었는데, 예를 들어 components/HomePage/, components/DetailPage/, components/Cart/ 같은 식으로 페이지별로 그룹화하는 것이 맞는지 궁금합니다.

4. 네이밍 컨벤션

같은 맥락으로 코드베이스 전반에 걸쳐 함수명, 변수명, 파일명을 일관되게 사용하려고 노력했지만, 실제로 실무에서 사용하는 네이밍 컨벤션과 비교했을 때 개선할 점이 있는지 확인하고 싶습니다. 또한 언급한 네이밍 컨벤션들(함수명 prefix, 파일명 케이스, 복수형 사용 등)이 실무에서 표준화된 규칙인지, 아니면 팀이나 프로젝트에 따라 달라지는 취향 차이인지 궁금합니다. 코드 리뷰 시 네이밍 컨벤션 불일치를 어떻게 지적하고 개선하는지도 궁금합니다.

  • 고민 사항:
    • setup*, handle*, update* 같은 prefix를 사용하는 것이 일반적인지, 그리고 일관성 있게 사용하고 있는지 확인하고 싶습니다.
    • ensureRendered() 같은 함수명이 직관적인지, 아니면 renderCartModal() 같은 이름이 더 나은지 궁금합니다.
    • 파일명에서 homepageEvents.js처럼 복수형을 사용하는 것이 적절한지, homepageEvent.jshomepageEventHandler.js 같은 이름이 더 나은지 조언해 주실 수 있을까요?
    • 컴포넌트 파일명을 CartModal.js처럼 PascalCase로 하는 것이 일반적인지, 아니면 cartModal.js처럼 camelCase가 나은지도 궁금합니다.

4팀 코드리뷰

제 모든 경력과 인생을 통틀어 코드리뷰 경험이 전무해서 팀원들 코드를 보며 어떤 리뷰를 해줄 수 있을지에 대해서도 조금 많이 막막한데요 ...
어쩌면 제일 기본적인 것들이 궁금합니다.

  • 디렉터리 구조
    현재 구조가 api, components, pages, router, store처럼 기능 단위로 나뉘어 있는데, 라우터·스토어·이벤트 같은 공통 로직을 더 세분화하거나 레이어 기준으로 묶는 게 나을지 궁금해요.
    예를 들어 router/enhancers처럼 서브 폴더를 둔 방식이 일관성 있는지, 혹은 더 단순화하는 게 좋을지 검토 부탁드립니다.
  • 네이밍/컨벤션
    혼자 일할 때는 보편적인 네이밍 규칙을 알 수 없어서 지피티한테 변수 혹은 파일 네이밍을 자주 부탁하곤 했었는데, 팀 내에서 이런 것도 통일하여 사용하나요? 현재 제 코드에는 registe와 create, setup과 init, bind와 render 같은 비슷한 상황에서 사용되는 단어들이 혼재되어 있는데, 각 동사가 정확히 어떤 상황에서 어떤 단어로 쓰여야 하는지, 또는 하나로 통일하는지... 어떤 방식을 참조하여 채택하는지 궁금합니다.
  • 컨벤션 관리 방식
    이건 그냥 궁금한 건데요, 사전에 문서화된 컨벤션 파일을 두고 코드리뷰에서 지적하는지, 아니면 PR마다 케이스 바이 케이스로 논의하는지 경험을 듣고 싶어요.
    새 규칙을 정할 때 ‘예시 코드’나 ‘린트 규칙 추가’ 중 무엇을 선호하는지도 알려주세요. (저는 ... 린트도 처음 써봐요 .........)

@joshuayeyo
Copy link

joshuayeyo commented Nov 15, 2025

Code Review

Q1

저는 router/index.js로 named exports 하고, /router/enhancers/~ 이렇게 했을 것 같습니더. (물론 이번엔 과제 개판으로 해서 저렇게 못함,,)

Q2

평소에는 통일하지만, 이번 과제에선 안했던 것 같슨..
전 보통 저기서 create | init | render 로 통일한 것 같아요
제 파일 중 CODING_STANDARDS처럼 코딩 표준 이라는 이름으로 팀원과 협의 후 맞추고 시작하는 편입니더

Q3

저는 팀 위키 노션 | 페이지 활용해서 둘 다 쓰는 편입니다. 작업 전 보통 husky로 논의하게 되고, 추가하고 싶으면 슬랙 등으로 이슈 올리고 협의해서 데일리 스크럼에서 논의 후 확정하는 편..?
애초에 ESLint & Prettier 올려두고 husky랑 githubActions 로 관리하는 편이긴 합니다..

Copy link
Contributor

@JunilHwang JunilHwang left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 피드백은 n8n + ai (gpt-5-mini)를 활용하여 자동으로 생성된 내용입니다.

전체 리뷰 요약

이번 PR은 기능 구현 요구사항을 잘 반영하여 SPA 완성도를 높인 점이 매우 인상적입니다. 특히 제품 목록 로딩, 필터/검색, 무한 스크롤, 장바구니 상태 관리, 토스트 메시지, URL 기반 상태 복원 등 실무에서 쓰이는 핵심 기능을 꼼꼼히 구현하셨습니다.

현재 프로젝트 아키텍처 주요 특징

  • 기능별 폴더 분리(api, components, events, pages, router, store, utils 등)로 관심사 분리가 잘 되어 있음
  • 옵저버 패턴 및 이벤트 버스(eventBus)를 활용한 상태 관리 및 컴포넌트 간 느슨한 결합
  • 커스텀 라우터 설계 및 enhancer 패턴 적용으로 라우터 확장성 고려
  • 상태 변화에 따른 UI 업데이트 및 이벤트 위임 패턴 일부 적용 중

확장성과 유지보수 관점 개선 제안

  • 이벤트 리스너 관리: 동적 DOM 요소에 대한 반복적인 이벤트 리스너 등록/제거 대신 이벤트 위임 패턴을 본격 적용해 메모리 누수와 성능 이슈 완화 가능
  • 컴포넌트 분리 및 역할 명확화: 렌더링과 상태 업데이트, 이벤트 핸들링 기능을 컴포넌트 내에서 명확히 분리하면 유지보수와 테스트에 유리
  • 라우터 매칭과 URL 관리: 경로 매칭 강화 및 URL 파라미터 처리를 모듈화해 향후 기능 확장 대비
  • 네이밍과 폴더 구조 정교화: 컴포넌트 파일명 통일, 이벤트 핸들러 위치 재검토 및 중첩 폴더 깊이 조절 등이 도움이 됨

종합적으로 현재 구조는 실무에서 충분히 활용 가능한 형태이며, 소규모에서 중간 규모 SPA에 적합합니다. 다만, 팀 협업과 프로젝트 확장 시 위 제안들을 바탕으로 리팩토링 해 나가면 더욱 견고한 아키텍처가 될 것입니다.## 1. CartModal.js 이벤트 리스너 관리

현재 setupCartModal에서 렌더링 후 기존 리스너를 제거(removeEventListener)하고 다시 등록하는 패턴을 사용 중이신데, 기본적으로는 메모리 누수를 방지하는 좋은 습관입니다. 그러나 실제로 매번 동일한 리스너를 제거하고 다시 추가하는 것은 비용이 있고, 특히 이벤트 대상이 많을 경우 성능에 영향을 줄 수 있습니다.

더 효율적인 방법은 이벤트 위임(event delegation) 방식을 적극 활용하는 것입니다. 즉, 변경이 잦은 하위 요소 각각에 리스너를 붙이지 않고, 상위 컨테이너 한 곳에 이벤트 리스너를 달아놓고 이벤트 버블링을 통해 실제 이벤트 타겟을 판별하여 처리하는 방법입니다. 이렇게 하면 리스너 중복 등록이나 관리가 훨씬 간단해지고, 메모리 사용도 최적화됩니다.

isSetup 플래그를 활용해 setupCartModal이 중복 실행되지 않도록 한 것은 적절한 패턴이며, 이벤트 위임과 결합할 경우 더욱 견고해질 수 있습니다.


2. 이벤트 리스너 Cleanup 로직

SPA 구조에서 페이지 전환 없이 상태나 화면을 변경하는 경우, 메모리 누수나 이벤트 중복 문제가 발생하지 않도록 이벤트 리스너를 명확히 정리(cleanup)하는 것이 베스트 프랙티스입니다. 특히 다음 경우에 cleanup이 필요합니다:

  • 컴포넌트가 라이프사이클이 존재하는 프레임워크(React 등)처럼 생성/제거를 반복하는 경우
  • 라우터 변경에 의해 같은 컴포넌트가 재생성되어 이벤트가 중복 등록되는 경우

현재 프로젝트처럼 컴포넌트가 주로 수동 렌더링으로 관리된다면, 이벤트 리스너를 router.subscribe 구독과 함께 등록하고, 이전 구독을 해제하는 패턴으로 관리할 수 있습니다. 예를 들어,

let cleanup = () => {};
router.subscribe(() => {
  cleanup();
  cleanup = bindEventHandlers();
});

이처럼 구독 해제 함수를 항상 반환하고 관리하면 메모리 누수와 중복 실행 문제를 막을 수 있습니다.

만약 앱 규모가 커지거나 복잡해지면 이벤트 관리 라이브러리를 쓰거나, 프레임워크 도입도 고려할 옵션입니다.


3. 디렉토리 구조와 모듈 분리

현재 src/ 아래 api, components, events, pages, router, store, utils 등의 기능 구분은 매우 일반적이고 협업에서도 익숙한 구조입니다. 아래 참고하세요:

  • events 폴더 위치: 이벤트 핸들러를 events/에 모으는 것도 가능하지만, 관련 컴포넌트나 페이지와 밀접하게 연관되는 이벤트라면 해당 컴포넌트 혹은 페이지 폴더 내에 두는 것도 유지보수에 유리합니다. 대형 프로젝트라면 features/ 폴더 단위(또는 컴포넌트별)로 이벤트 관리하는 게 좋습니다.
  • router/enhancers 중첩: enhancer별로 라우터 확장 기능을 분리하는 구조도 모듈성과 관심사 분리에 좋습니다.
  • 컴포넌트 그룹화: components/를 그냥 공통 컴포넌트 모음으로 둘 수도 있지만, 페이지 별(HomePage/, DetailPage/, Cart/)로 폴더를 나눠 관련 컴포넌트를 묶는 게 명확한 대형 프로젝트에서는 추천됩니다. 이 경우 컴포넌트 재사용 범위를 고려해 공용 컴포넌트는 common/ 폴더 등으로 별도 분리하면 좋습니다.

요약하면, 지금 구조는 학습과 작은 팀에 적합하며, 확장 시 위와 같이 재구성하거나 혼합해서 사용하는 것이 일반적입니다.


4. 네이밍 컨벤션

  • 함수명 prefix: setup*, handle*, update* 등 직관적 기능 기반 prefix는 실무에서 널리 사용됩니다. 일관성 있게 사용하는 것이 중요합니다.

  • 함수명 명확성: ensureRendered() 같은 이름은 기능이 불명확할 수 있어, 보통 renderCartModal() 혹은 updateCartModalUI() 같은 명확한 네이밍이 선호됩니다.

  • 파일명:

    • 이벤트 파일명에 복수형(homepageEvents.js) 사용도 흔하지만, EventHandlers.js 또는 EventManager.js 같이 좀 더 기능적으로 네이밍하는 방식도 있습니다.
    • 컴포넌트 파일명은 PascalCase (CartModal.js)가 더 표준적이며, React 등 대부분 프레임워크와 일관됩니다.
  • 지향점:

    • 네이밍 컨벤션은 팀/프로젝트 기준에 따라 다르므로, 사전에 컨벤션 문서를 만들고 이를 코드 리뷰에서 공유 및 점검하는 것이 이상적입니다.
    • 리뷰 시 네이밍 불일치는 "의미가 모호하거나 혼란을 줄 수 있는 부분"을 구체적으로 지적하고, 더 명확한 대안을 함께 제시하는 방식이 효과적입니다.

5. 컨벤션 관리 방식

  • 경험상 컨벤션은 초기 문서화(README, Wiki, 별도 컨벤션 가이드)를 기본으로 하며, 린트(ESLint, Prettier 등)를 통해 자동화하는 게 매우 효과적입니다.
  • 코드 리뷰에서 컨벤션 위반은 가능한 자동화 도구에 맡기고, 공유된 규칙에서 벗어난 부분에 대해 가볍게 피드백 하는 형태입니다.
  • 새 규칙 도입 시 예시 코드를 첨부하거나, 린트 룰을 추가해 적용 사례를 명확히 제시하는 방식을 선호합니다.
  • 초기에는 개인 프로젝트 기준 컨벤션을 참고하여 최소한의 규칙부터 시작해서
    차근차근 팀에 맞게 발전시키는 과정이 자연스럽습니다.

<span class="text-lg font-bold text-gray-900">총 금액</span>
<span id="cart-modal-total-price" class="text-xl font-bold text-blue-600">${totalPrice.toLocaleString()}원</span>
</div>
<!-- 액션 버튼들 -->
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1. CartModal의 이벤트 리스너 관리

setupCartModal에서 동적 생성 요소에 대해 기존 리스너 제거 후 재등록하는 방식을 사용하고 있습니다.

  • 현재 방식은 메모리 누수를 어느 정도 방지할 수 있으나, DOM에 많은 요소가 있을 경우 불필요한 removeEventListener/addEventListener 호출이 반복되어 성능 저하 우려가 있습니다.
  • 더 효율적인 방법으로는 '이벤트 위임(event delegation)' 패턴 사용을 권장합니다. 상위 컨테이너(예: #cart-modal)에 이벤트를 한 번만 바인딩하고, 이벤트 버블링을 활용해 이벤트 대상 요소를 판별하는 방식입니다.
  • isSetup 플래그를 통한 중복 설정 방지는 적절하나, SPA에서 컴포넌트가 자주 리렌더링 되거나 언마운트/마운트 시 이벤트 구독 정리가 필요할 수 있어 보완해야 합니다.

<div class="text-gray-400 mb-4">
<svg class="mx-auto h-12 w-12" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M3 3h2l.4 2M7 13h10l4-8H5.4m2.6 8L6 2H3m4 11v6a1 1 0 001 1h1a1 1 0 001-1v-6M13 13v6a1 1 0 001 1h1a1 1 0 001-1v-6"></path>
</svg>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2. CartModal 초기 렌더링 및 업데이트 로직 분리

렌더링 로직과 상태 업데이트를 한 함수에서 처리하는 구조인데, 이로 인해 함수가 복잡하고 유지보수가 어려워질 수 있습니다.

  • 상태 변경과 DOM 렌더링을 별도의 함수로 분리하면 테스트 및 확장이 용이해집니다.
  • 예를 들어, 상태 변화시 변경할 부분(체크박스 상태, 총 금액 표시 등)을 업데이트하는 함수와 전체 UI를 다시 그리는 함수로 구분할 수 있습니다.

let state = {
homepage: createHomepageState(),
cart: loadCartFromStorage(),
};
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3. 상태 관리 - 불변성 및 저장소 일관성 유지

  • 상태 변경 시 객체를 불변하게 다루고 있음은 좋은 패턴입니다.
  • 다만 updateCartupdateHomepage 함수 내에서 복잡한 상태 변경 시 최소한의 변화를 감지하고 최소한으로 상태를 변경하는 최적화가 가능합니다.
  • 이 컴포넌트는 전체 상태를 한 군데서 관리하고 있어, 추후 기능 확장 시 상태 분리 혹은 컴포넌트별 별도 모듈화 고려해야합니다.


if (loading !== undefined) {
sentinel.dataset.loading = loading ? "true" : "false";
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4. 이벤트 핸들러 내 비동기 로직 처리

  • handleProductsLoadMore는 비동기 API 호출과 상태 업데이트가 섞여 있습니다.
  • Promise의 에러 처리를 로그만 하는 방식이기 때문에 개선 여지가 있습니다.
  • 에러 발생 시 사용자에게 명확한 UI 에러 상태와 재시도 기능 제공에 주의를 기울일 필요가 있습니다.

const html = await Promise.resolve(viewFactory(context));

target.innerHTML = html;
this.notify(context);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5. Router 매칭 알고리즘 한계 및 발전 가능성

  • 현재 경로 매칭 시 파라미터만 부분 처리하며, 복잡한 경로나 와일드카드 지원은 없습니다.
  • 향후 라우터 확장이나 다이나믹 세그먼트 지원이 필요하면 외부 경로 매칭 라이브러리 사용 또는 매칭 기능 확장이 권장됩니다.
  • 라우터 내 URL 조작 시 getBasePath 로직에서 환경 변수 활용은 적절하나, 배포 환경이 다양해질 경우 이 부분을 더 유연하게 개선할 수 있습니다.

const toastConfig = {
success: {
bgColor: "bg-green-600",
icon: `<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

7. Toast 컴포넌트 디자인과 동작 분리

  • Toast 타입별 설정값 및 렌더링을 객체로 분리해 관리하는 점은 좋은 설계입니다.
  • 다만 반복적으로 id toast-close-btn를 여러 컴포넌트에 사용하는 것은 DOM 내 중복 id 문제가 생길 수 있어,
    클래스명이나 data-attribute 사용이 더 적절합니다.
  • 자동 사라지는 기능과 수동 닫기 기능이 명확히 분리되어 잘 동작하는지 확인이 필요합니다.

@@ -0,0 +1,155 @@
const renderRelatedProduct = ({ productId, title, image, lprice }) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

8. 파일명 오타 및 표기 일관성

  • 파일명이 ProductDetali.js로 오타가 있습니다. ProductDetail.js가 올바른 명칭입니다.
  • 실제 파일명과 export된 컴포넌트를 일치시키는 것이 팀 협업 시 헷갈림 방지 및 자동 완성에 유리합니다.

@@ -0,0 +1,32 @@
// src/utils/eventBus.js
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

9. EventBus 구현 및 확장성

  • 단순 이벤트 버스 구현이 잘 되어 있어 느슨한 결합 구조를 잘 지원하고 있습니다.
  • 다만 잠재적인 메모리 누수를 막기 위해 이벤트 리스너를 적절히 제거하는 것이 매우 중요합니다.
  • 만약 이벤트 체인이 복잡해지면 독립적인 Pub/Sub 라이브러리 도입도 고려할 수 있습니다.

});
}
}
import { createRouter } from "./router/Router.js";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

10. 진입점(main.js) 코드 구조 및 비동기 흐름

  • 라우터 생성과 이벤트 등록, 상태 초기화가 잘 분리되어 있으며 순서도 명확합니다.
  • enableMocking() 호출 시 import 동적 분리와 서비스워커 URL 명시가 좋은 모던 방식입니다.
  • 다만 라우터와 컴포넌트 간의 의존성을 더 느슨하게 분리하거나,
    코드 스플리팅을 활용하면 초기 로딩 속도 개선이 가능합니다.

@@ -0,0 +1,69 @@
import { getCartState, subscribe } from "../store/appStore.js";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11. Header 컴포넌트 및 상태 구독 방식

  • setupHeader에서 상태 변경 시 배지 업데이트 구독을 관리하는 점이 매우 좋습니다.
  • 다만, 현재 구독 해제(cleanup) 함수가 없어 만약 라우터 재생성이나 페이지 전환 시 이벤트 중복 등록 가능성이 있습니다.
  • 향후에는 이벤트 및 상태 구독 관리를 위한 명시적 cleanup 패턴 적용을 권장합니다.

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.

3 participants