Skip to content

Conversation

@minjeeki
Copy link

@minjeeki minjeeki commented Nov 9, 2025

과제 체크포인트

배포 링크

기본과제

상품목록

상품 목록 로딩

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

상품 목록 조회

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

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

  • 드롭다운에서 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 활용 경험 공유하기

리뷰 받고 싶은 내용

@JunilHwang JunilHwang force-pushed the main branch 3 times, most recently from c1cdc92 to 9b09aa3 Compare November 10, 2025 11:59
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에서는 요구사항에 맞는 상품 목록, 장바구니, 상품 상세 페이지 등 주요 기능들이 충실히 구현되어 있습니다. 특히 API 통신, 로딩 UI, 카테고리 네비게이션, 장바구니 모달 UI 및 상태 관리를 잘 구성했습니다. 하지만 현 구조는 상태 관리와 이벤트 핸들링이 컴포넌트 또는 페이지 내에 국한되어 있어 대규모 확장 및 유지보수 시 한계가 보입니다.

  • 현재 상태는 함수 내 지역변수와 DOM 이벤트에 의존하는 방식으로 관리되어 있으며, 무한 스크롤, URL 상태 동기화, 전역 상태 공유 관점에서 개선책이 필요합니다.
  • 이벤트 처리에서 중복되는 로직(특히 상품 상세 이동)은 이벤트 위임 및 조건 단순화를 통해 함수 중복을 제거할 수 있습니다.
  • 장바구니 상태 관리가 모달 내부에 집중되어 있어 코드 가독성 및 확장성이 다소 제한적입니다. 상태 저장소 분리 및 구독 패턴 도입을 권장합니다.
  • API와 렌더링 함수가 잘 분리되어 있으며, UI 컴포넌트 재사용성도 양호합니다.
  • 404 페이지와 토스트 메시지 등 사용자 경험 측면도 충실히 구현하여 프로젝트 전반적인 완성도가 높습니다.

개선 방향 예시

[현재]
- 상태 : 지역 변수, 이벤트 위임 미흡
- 이벤트 : 중복조건 분기 많음
- 장바구니: UI + 상태 + 이벤트가 분리 미흡

[개선]
- 상태 관리 모듈 도입 (observable store 등)
- 상품 상세 이동 이벤트 간소화
- 장바구니 모듈 분리 및 이벤트 정책 개선
- 무한 스크롤 구현 및 API 호출 누적
- URL 쿼리와 상태 동기화 일원화

이런 개선을 통해 확장성과 유지보수성이 크게 향상될 것입니다.추가 질문이 없으므로, 본 리뷰를 바탕으로 전반적인 아키텍처 설계 방향과 코드 품질을 고려하여 향후 기능 확장과 유지보수에 대비하는 구조 개선을 추천드립니다. 상태 관리와 UI 컴포넌트 분리를 점진적으로 도입하면서, 일관된 이벤트 처리 모범 사례(이벤트 위임, 상태 구독 등)를 적용한다면, 대규모 프로젝트로 이동하더라도 효율적인 개발이 가능합니다.

편리한 디버깅과 테스트를 위해 각 기능 단위별 모듈화를 지속적으로 추진해보세요. 현재 기본 구현이 명확하고 기능별로 재사용 가능하게 작성된 점은 매우 훌륭하며, 이 기반 위에 개선점을 적용하면 좋은 결과를 얻을 수 있습니다.

</div>
</div>
</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. 문제상황 제시

구체적인 문제 상황:
현재 상품 목록 페이지는 로딩 상태, 제품 목록 출력, 카테고리 선택, 검색, 정렬 기능 등이 잘 구현되어 있으나, 대용량 상품 데이터 처리 및 무한 스크롤 확장 시 상태 관리 및 성능 이슈가 발생할 수 있습니다.

현재 코드의 한계:

  • 상태가 컴포넌트 내의 지역변수로 관리되어 있는데, 페이지 이동이나 상태 복원이 필요한 경우(예: 심화과제에서 URL 쿼리와 상태 동기화) 확장 및 유지 관리가 어려움
  • 무한 스크롤 구현이 보이지 않으며, 페이지 단위 로딩 시 전체 데이터를 재설정하는 방식을 사용해 누적 렌더링 및 페이지네이션 확장에 비효율적임
  • 각 필터와 정렬 상태 변경 시 새로고침 없이 상태를 적절히 유지하면서도 UI를 효율적으로 갱신하는 구조가 분명하지 않음

2. 근본 원인

핵심 문제:
상태 관리와 렌더링 로직이 완전히 컴포넌트 함수 스코프 내 변수로 관리되고 있어, 상태 동기화 및 확장성 측면에서 한계가 있습니다.

왜 문제인가:

  • 상태 변경과 UI 재렌더링이 명확한 분리 없이 혼합되어 있어 새로운 요구사항(예: URL 쿼리와 상태 동기화, 인피니트 스크롤, 다른 컴포넌트와 상태 공유) 적용 시 코드 복잡도가 급격히 증가합니다.
  • 무한 스크롤 같이 점진적 데이터 추가시 기존 데이터 누락 위험 및 비효율이 발생할 수 있습니다.

3. 개선 구조

현재 구조:

  • 상태: 컴포넌트 지역 변수(
    let selectedCategory1, selectedCategory2, currentSearch, currentSort, currentLimit, currentPage
    )
  • 렌더링: 상태 변경 시 함수 호출로 DOM 재생성
  • API 호출: 현재 상태 기준으로 전체 상품 재요청 및 렌더링

개선된 구조:

  • 상태: 전역 또는 페이지 단위 상태 객체로 관리하는 Central Store(예: 간단한 Observable 객체 혹은 이벤트 기반 상태 관리)
  • 렌더링: 상태 변경 감지 시 필요한 부분만 업데이트하는 함수 분리
  • API 호출: 무한 스크롤 구현 시 기존 데이터 누적 유지 및 신규 데이터만 추가
  • URL 상태 동기화: 상태 변경 시 URL 반영과 URL 변경 시 상태 복원 로직 분리

개선 사항:

  • [상태 관리 모듈 도입] 페이지 상태를 한곳에서 관리하여 변경을 감지하고, 상태 변경 시 구독자(렌더링 함수 등)를 호출하는 구조로 개선
  • [무한 스크롤 구현 및 최적화] 스크롤 이벤트 핸들러를 추가하여 하단 근처 도달 시 다음 페이지 데이터 요청 및 기존 상품 리스트에 추가
  • [로딩 및 에러 상태 분리] 별도의 UI 컴포넌트로 로딩/에러 상태를 관리하며, 재시도 기능을 추가해 사용자 UX 향상

코드 비교 예시:

// ❌ 현재 방식
let currentPage = 1;
async function loadProducts() {
  const response = await getProducts({ page: currentPage, ...filters });
  renderProducts(response.products);
}

// ✅ 개선된 방식
const state = { products: [], page: 1, filters: {}, loading: false };
const subscribers = [];

function setState(partial) {
  Object.assign(state, partial);
  subscribers.forEach((fn) => fn(state));
}

function subscribe(fn) {
  subscribers.push(fn);
}

async function loadMoreProducts() {
  setState({ loading: true });
  const { products, page, filters } = state;
  const response = await getProducts({ page, ...filters });
  setState({ products: [...products, ...response.products], loading: false, page: page + 1 });
}

// 스크롤 이벤트 등으로 loadMoreProducts 호출 가능
// 렌더링 함수는 subscribe로 연결되어 자동 갱신

</div>
</div>
<!-- 하단 액션 -->
<div class="sticky bottom-0 bg-white border-t border-gray-200 p-4">
Copy link
Contributor

Choose a reason for hiding this comment

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

1. 문제상황 제시

구체적인 문제 상황:
장바구니 데이터가 localStorage에 저장되고 모달 UI에서 관리되지만, 상태 변경과 이벤트 로직이 모달 컴포넌트 내부에 과도하게 몰려 있어 앞으로 기능 확장 시 가독성 및 유지보수성이 저하될 수 있습니다.

현재 코드의 한계:

  • localStorage 직접 조작+UI 업데이트 로직 혼재로 커플링 심함
  • 이벤트 리스너가 모달 내 다양한 액션별로 일괄 등록되어 있어 분리가 어렵고 테스트도 불편
  • 상태 변경 시 이벤트와 UI 갱신이 전역 window 이벤트와 함수 호출에 의존

2. 근본 원인

핵심 문제:
상태 관리와 뷰 렌더링 애플리케이션 로직 및 이벤트 핸들링이 한 컴포넌트 모듈 안에 강하게 결합되어 있어 재사용성과 확장성이 저하됨

왜 문제인가:

  • 복잡한 비즈니스 로직이 하나의 거대한 모듈로 구현되면, 기능 추가시 수정 범위가 커지고 버그가 유입되기 쉬움
  • 다른 컴포넌트 및 UI와 장바구니 상태를 공유하거나 독립 테스트가 어려움

3. 개선 구조

현재 구조:

  • localStorage <-> 컴포넌트 내 상태 관리
  • 이벤트 리스너: 모달 전체 오버레이에 집약
  • UI 렌더링: 상태 변경 마다 전체 innerHTML 변경

개선된 구조:

  • 상태 관리 분리 (예: CartStore 형태 모듈)
  • 상태 변화 감지 및 옵저버 패턴 활용
  • UI 컴포넌트는 상태 구독자 역할 수행
  • 이벤트 핸들러도 해당 컴포넌트 내 역할 분리

개선 사항:

  • [상태 저장소 설계] Cart 상태를 별도의 CartStore 객체로 분리하여 상태 변경과 구독을 관리
  • [컴포넌트 분리] Cart 모달 UI 렌더링 함수와 이벤트 핸들링 함수를 분리하여 역할 명확화
  • [상태 반영 최적화] 상태 변경 시 전체 재렌더링 대신 필요한 DOM 업데이트만 수행

코드 비교:

// ❌ 현재 방식 - localStorage 직접 조작 + 이벤트 혼재
function saveCartData(data) {
  localStorage.setItem('shopping_cart', JSON.stringify(data));
  window.dispatchEvent(new CustomEvent('cartUpdated'));
  updateCartContent();
}

// ✅ 개선된 방식 - 상태 저장소와 이벤트 분리
class CartStore {
  constructor() {
    this.state = { items: [], selectedAll: false };
    this.listeners = [];
  }

  getState() { return this.state; }
  setState(newState) {
    this.state = { ...this.state, ...newState };
    this.listeners.forEach((cb) => cb(this.state));
    localStorage.setItem('shopping_cart', JSON.stringify(this.state));
  }

  subscribe(cb) { this.listeners.push(cb); }
}
const cartStore = new CartStore();

// 컴포넌트는 cartStore.subscribe로 상태 반영을 받음

};
}

// 경로 매칭
Copy link
Contributor

Choose a reason for hiding this comment

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

1. 문제상황 제시

구체적인 문제 상황:
현재 라우터는 기본적인 라우팅과 URL 파싱 기능을 구현하고 있으나, 쿼리 문자열 처리 및 동적 경로 매칭 코드가 혼재되어 복잡하며, 경로 정규식 생성 및 파라미터 추출이 조금 불필요하게 복잡합니다.

현재 코드의 한계:

  • pathToRegex 함수가 regex와 키 배열을 묶어 객체로 반환하지만, matchRoute에서 일일이 참조
  • 쿼리 파라미터는 단순 객체로 반환하지만 타입 안정성 및 중복처리가 없음
  • URL 쿼리 문자열 빌드 시 빈 값 필터링이 수동적
  • navigate 함수 내에서 경로 매칭과 히스토리 관리, 핸들러 실행 코드가 많이 섞여 있음

2. 근본 원인

핵심 문제:
경로 매칭 로직과 URL 쿼리 관리 로직이 한 클래스 내부에서 섞여 있고, 경로 정규식 및 파라미터 처리 로직이 복잡하며, 목적에 맞게 분리되지 않음

왜 문제인가:
이로 인해 유지보수 및 기능 확장 시 어려움이 있고, 다중 라우트 매칭 조건 처리 혹은 SPA 최적화에 한계가 있습니다.

3. 개선 구조

현재 구조:

  • Router 클래스 내에 모든 기능 집중
  • 경로 -> 정규식, 파라미터 처리 직접 관리
  • 쿼리 파라미터 수작업 처리

개선된 구조:

  • 라우팅 경로 및 파라미터 처리 라이브러리 도입(예: path-to-regexp), 혹은 관련 함수 분리
  • 쿼리 파라미터 처리 유틸 함수 분리 및 타입 안정성 강화
  • navigate와 route handler 호출 역할 분리

개선 사항:

  • [경로 정규식 관리 별도 모듈 또는 단순화]
  • [URL 파싱 및 쿼리 문자열 빌드는 유틸 함수로 분리]
  • [히스토리 관리는 별도 함수 또는 미들웨어 패턴 적용]

예시 (쿼리 문자열 빌드 개선):

buildQueryString(query) {
  return "?" + Object.entries(query)
    .filter(([_, v]) => v !== undefined && v !== null && v !== "")
    .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
    .join("&");
}

}

// 상품 목록으로 돌아가기 버튼
const goToProductListBtn = event.target.closest(".go-to-product-list");
Copy link
Contributor

Choose a reason for hiding this comment

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

1. 문제상황 제시

구체적인 문제 상황:
main.js에서 이벤트 위임을 이용해 여러 곳에서 상품 상세 페이지로 이동하도록 구현되어 있으나, 클릭 이벤트 처리 로직이 다소 중복되고 조건 분기가 깊어 가독성 및 유지보수성에 부담이 있습니다.

현재 코드의 한계:

  • 여러 이벤트 탐색(productCard, productInfo, productImage, relatedProductCard)를 반복적으로 확인함
  • 상품 상세 이동 조건이 각기 별도로 처리되어 중복 증가
  • 코드가 중복되어 추가적인 이벤트 핸들링 확장 시 버그 발생 위험 증가

2. 근본 원인

핵심 문제:
상품 상세 페이지 이동을 위한 이벤트 처리 로직이 중복 반복되고 분기 구조가 길어짐

왜 문제인가:

  • 중복 코드는 버그 발생 우려와 유지보수 부담을 높임
  • 행사 발생 시 디버깅 및 확장성에 한계

3. 개선 구조

현재 구조:

if (productCard) { /* navigate */ }
else if (productInfo) { /* ... */ }
else if (productImage) { /* ... */ }
else if (relatedProductCard) { /* ... */ }

개선된 구조:

  • 공통 클래스 또는 데이터 속성(data-product-id)이 있는 최상위 요소 하나만 찾도록 이벤트 위임 간소화
  • 클릭된 요소에서 가장 가까운 data-product-id를 가진 요소를 찾고, 거기서 productId 획득 후 navigate 적용

비교 코드:

const productElement = event.target.closest('[data-product-id]');
if (productElement) {
  const productId = productElement.getAttribute('data-product-id');
  if (productId) {
    router.navigate(`/product/${productId}`);
  }
}

// 현재 장바구니 데이터 가져오기
const cartData = getCartData();
const { items } = cartData;

Copy link
Contributor

Choose a reason for hiding this comment

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

1. 문제상황 제시

구체적인 문제 상황:
장바구니에 담기 버튼 컴포넌트에서 상품 정보를 data attribute에 JSON 문자열로 인코딩해 저장하고 있는데, 데이터 크기가 커질 경우 DOM 속성에 부담이 될 수 있으며, 복잡성도 증가할 수 있습니다.

현재 코드의 한계:

  • 대용량의 JSON을 data-product 속성에 문자열화하여 삽입하면 HTML 이스케이프 문제 및 성능 저하 가능성 있음
  • JSON 파싱과 디코딩 과정에서 예외 발생 시 안전장치가 충분하지 않음

2. 근본 원인

핵심 문제:
상품 정보를 직접 DOM 속성에 JSON으로 저장하여 의존성과 유지보수성이 저하됨

왜 문제인가:

  • 데이터와 UI가 강하게 결합되어, 데이터 변경 시 UI를 모두 수정해야 함
  • 무거운 JSON 문자열을 DOM에 저장하면 렌더링 성능 저하 가능

3. 개선 구조

개선 사항:

  • [상품 ID 등의 최소한의 식별자만 data attribute로 저장하고, 필요한 전체 상품 정보는 별도의 객체에 보관]
  • [이벤트 처리 시 data-product-id로 식별 후, 중앙 상품 저장소에서 상세정보 조회]
  • [JSON.stringify 대신 안전한 직렬화 방식을 고려]

개선 예시:

// 상품 정보는 전역 객체 또는 모듈에서 관리 const productsCache = {};
// 이벤트 시
const productId = button.getAttribute('data-product-id');
const product = productsCache[productId];

// 이렇게 하면 DOM에 부담이 적음.

// localStorage 초기화
initCartStorage();

// 라우트 등록
Copy link
Contributor

Choose a reason for hiding this comment

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

가독성 및 퍼포먼스

  • 여러 DOM 조회 (event.target.closest)를 반복하는 부분을 최소화할 수 있습니다.
  • 네비게이션 관련 이벤트 핸들러를 간소화시켜 유지보수성을 높일 수 있습니다 (앞서 리뷰에서 제안).
  • 페이지 렌더링과 라우팅 로직이 분리되어 있고 명확히 역할이 나누어져 있습니다.

Suggestion: 상품 상세 페이지 이동 이벤트를 하나의 통합 이벤트 핸들러로 단순화해보세요.

// 초기 opacity 설정 및 애니메이션
toastElement.style.opacity = "0";
toastElement.style.transition = "opacity 0.3s ease-in-out";

Copy link
Contributor

Choose a reason for hiding this comment

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

사용자 경험 개선

  • 토스트 닫기 버튼과 자동 닫기 로직을 잘 구성했으며, 애니메이션 효과도 포함되어 UX가 우수합니다.
  • 단, 같은 토스트가 연속으로 표시될 때 중복 표현을 허용하는 구조입니다.

Suggestion: 중복 토스트 중복 표시 방지 로직(예: 동일 메시지 중복 대기 후 새 표시 금지)을 고려하면 사용자 경험이 보다 개선될 수 있습니다.

<main class="max-w-md mx-auto px-4 py-4">
${content}
</main>
${Footer()}
Copy link
Contributor

Choose a reason for hiding this comment

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

구조 및 확장성

  • 레이아웃 함수가 재사용 가능하도록 header, main, footer를 일관되게 배치하였음.
  • 전역 토스트 컨테이너 id를 배치하여 모든 페이지에서 토스트 메시지를 공유할 수 있게 디자인함.

Suggestion: 향후 레이아웃을 컴포넌트 트리구조 형태로 바꾸거나 템플릿 분리하면 확장성 증가 및 유지보수성 향상에 도움이 됩니다.

// 상품 상세 조회
export async function getProduct(productId) {
const response = await fetch(`/api/products/${productId}`);
return await response.json();
Copy link
Contributor

Choose a reason for hiding this comment

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

API 통합 및 확장성

  • API 함수에 파라미터 기본값을 지정해 호출 편의성을 제공하였고, URLSearchParams 활용으로 가독성이 높음.
  • 비동기 함수들에 await 명확히 지정되어 응답 처리 과정 안정적임.

Suggestion: 에러 핸들링은 호출 측에 맡기고 있으나, 필요 시 공통 에러 처리기 도입도 고려 가능.

  • 추후 API 함수의 파라미터 구조를 명확히 인터페이스화 하면 인텔리센스 및 테스트 용이.

<main class="max-w-md mx-auto px-4 py-4">
<div class="text-center my-4 py-20 shadow-md p-6 bg-white rounded-lg">
<svg viewBox="0 0 320 180" xmlns="http://www.w3.org/2000/svg">
<defs>
Copy link
Contributor

Choose a reason for hiding this comment

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

UX 및 디자인

  • 404 페이지 SVG를 활용한 시각적 구성과 홈으로 이동하는 버튼 UI가 적절히 구현됨.
  • 헤더의 옵션도 적절히 설정하여 화면 일관성을 유지함.

Suggestion: 앞으로 404 혹은 에러 페이지에 네비게이션 히스토리 버튼(예: 뒤로가기) 추가하면 더욱 친절한 UX 제공 가능.

Copy link

@devchaeyoung devchaeyoung 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 네비게이션 등 주요 기능이 상당히 충실하게 구현되었습니다. 특히 API 모킹부터 로딩 상태, 오류 처리, 무한스크롤, 브레드크럼, 장바구니 내부 수량 조절까지 전반적인 관심사 분리가 잘 되어 있어 기능별 유지보수가 용이한 구조입니다. 👍

<추가질문>에서 요청한 셀프회고에 대해 답변하며, 핵심 질문은 실무 적용 가능성과 구조 개선, 추가 학습 방향에 집중되어 있습니다. 질문의 핵심에 대한 요약은 다음과 같습니다:

  1. Enter키 검색 기능 보완 필요: 현재 구현은 debounce 처리만 있으므로, 실제 요구사항인 키 입력 시 바로 검색하는 이벤트 처리가 추가되어야 합니다.
  2. 무한스크롤 활성화 조건: 홈 페이지 전용으로 조건 추가가 현실적 요구이며, 현재 구조는 뷰 단에서 이를 제어하기에 개선 여지가 있습니다.
  3. 장바구니 상태 관리: localStorage와 커스텀 이벤트로 분리했지만, 반복 호출 시 성능 최적화 고민이 필요하며
  4. SPA 내비게이션 이벤트 위임: 중복 코드 개선을 위한 함수 추출과 정제된 이벤트 관리를 고려해볼 수 있습니다.

전체적으로 작업자의 관심사 분리, 이벤트 위임, API 추상화, 지속가능한 UI 상태 관리에 대한 이해와 구현이 합리적으로 이뤄진 점이 인상적이며 향후 더 나은 확장성을 위해서는 상태관리를 보다 명확히 분리하고 이벤트 핸들링을 최적화하는 방향으로 발전해보면 좋겠습니다.

질문에대한 답변

1. 질문 요약

먼저 구현한 SPA 구조를 기반으로, Enter 키를 눌러 검색하는 기능과 무한스크롤 조건 제어가 명확한지, 그리고 장바구니 상태 및 이벤트 관리, SPA 내비게이션 이벤트 위임 관련해서 고민하셨네요. 또한, 기술적 성장과 학습 효과, 코드 구조 개선 방향에 대해 자문하시고 있습니다.

2. 현재 선택의 장단점

  • Enter키 검색 기능은 debounce 입력 이벤트만 잘 처리하고 있지만, Enter 키 전용 처리가 누락되어 있어 사용성 측면에서 부족합니다.
  • 무한스크롤은 IntersectionObserver를 활용해 깔끔히 구현했으나, 현재는 홈 페이지와 다른 페이지를 구분 없이 모두 작동할 수 있어서 페이지 별 정책 부여가 어렵습니다.
  • 장바구니는 localStorage를 중앙 상태로 활용하며 이벤트 기반 갱신을 실행해 복잡도를 낮췄지만, 바로바로 UI 업데이트가 다중 호출되는 경우 최적화 여지가 있습니다.
  • SPA 내비게이션의 이벤트 위임이 여러 요소에 대해 분리되어 있지만, 코드 중복 해소 및 함수 추출이 가능해 가독성·재사용성 개선 여지가 있습니다.

3. 실무에서라면 이렇게 설계할 것 같아요

  • Enter 키 입력을 별도 이벤트 핸들러로 구현하여 사용자 명확한 검색 트리거를 지원합니다.
  • 라우터 상태에 기반해 무한스크롤 활성 여부를 명확히 나타내도록 관리해 해당 페이지에서만 옵저버를 등록 혹은 해제합니다.
  • 장바구니 상태 업데이트를 debounce 혹은 배치 처리하여 불필요한 렌더링 비용을 줄이고, 더 나은 상태 관리 라이브러리(예: Redux, Zustand) 도입을 검토합니다.
  • 이벤트 위임 코드에서 공통 기능 추출 후 재사용하며, 내비게이션 로직을 단일 진입점으로 관리해 유지보수를 간결하게 만듭니다.

4. 앞으로 구조를 잡을 때 참고하면 좋은 포인트

  • 관심사 분리는 훌륭하니 유지하되, 상태 업데이트 로직과 UI 렌더링의 분리와 효율적 호출 정책을 고민해보세요.
  • 사용자 이벤트에 대해 명확한 이벤트 체인을 구축하고, 이벤트 핸들러 내 역할 분담, 입출력 정의를 고려합니다.
  • URL/query 상태와 뷰 상태가 1:1 대응하도록 설계하여 새로고침 등 사용자 행동에 일관된 복원력을 마련합니다.
  • 코드 중복은 유지보수 부담으로 연결되니, 기능 단위, 재사용 단위의 함수, 컴포넌트 분리에도 관심을 가지면 좋겠습니다.

이번 과제 수행 과정에서 이러한 점들을 의식적으로 검토해보시면서, 다음 단계에서 점진적 개선을 시도해보면 좋겠습니다. 계속 좋은 코드와 구조에 관심 가지시면서 발전하시면 충분히 좋은 실무 역량이 될 거에요! 👍

<div class="mb-4">
<div class="relative">
<input type="text" id="search-input" placeholder="상품명을 검색해보세요..." value="" class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg
focus:ring-2 focus:ring-blue-500 focus:border-blue-500">

Choose a reason for hiding this comment

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

검색 input 에 대해 디바운스(300ms)와 Enter 키 입력 시 검색 수행이 있는데, Enter 키 처리 이벤트가 보이지 않습니다. 요구사항에 'Enter 키로 검색이 수행된다'가 있기에, 아래와 같이 'keypress'나 'keydown' 이벤트에서 Enter 키를 잡아 검색이 실행되도록 별도 구현해보면 좋겠습니다.

searchInput.addEventListener('keydown', (e) => {
  if (e.key === 'Enter') {
    currentSearch = e.target.value.trim();
    resetAndLoadProducts();
  }
});

이렇게 하면 debounce 처리와 별개로 사용자가 명확히 검색 버튼 대신 Enter 키로도 검색을 할 수 있어 UX가 좋아집니다.

(category1) => `
<button data-category1="${category1}" class="text-left px-3 py-2 text-sm rounded-md border transition-colors
bg-white border-gray-300 text-gray-700 hover:bg-gray-50">
${category1}

Choose a reason for hiding this comment

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

무한스크롤 옵저버를 scrollObserver 변수에 담아 관리하는 점은 좋습니다. 다만, 현재 상태에서 홈 페이지에서만 무한 스크롤을 활성화하려면 뷰 컴포넌트에서 조건을 추가하거나, router 쪽에서 페이지 경로에 따라 옵저버 생성 여부를 결정할 필요가 있습니다. 지금 구조에서는 별도 조건 없이 항상 무한 스크롤이 작동할 수 있는데, 이는 추후 다른 페이지에 무한 스크롤이 필요한 경우에도 영향을 줄 수 있어요.

확장성을 고려한다면, 무한 스크롤 활성화 여부를 prop이나 인자로 받거나 페이지 경로를 검사하는 방식을 도입해보면 좋겠습니다.

hover:bg-gray-700 transition-colors text-sm">
전체 비우기
</button>
<button id="cart-modal-checkout-btn" class="flex-1 bg-blue-600 text-white py-2 px-4 rounded-md

Choose a reason for hiding this comment

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

장바구니 상태를 localStorage로 관리하며 커스텀 이벤트(cartUpdated)를 통해 UI 동기화를 시도하는 구조는 관심사 분리 측면에서 잘 되어 있습니다. 👍 다만, 수량 변경 콜백에서 window.updateCartContent()를 재귀처럼 호출하는 부분이 자주 발생하면 성능 저하가 있을 수 있어요.

여기서 한 번에 여러 개의 수량 변경 이벤트를 묶어서 처리하거나 이를 상태 관리 라이브러리로 분리하면 업데이트 비용을 줄일 수 있습니다(예: setTimeout으로 debounce). 실무에서는 상태 관리 전용 모듈이나 프레임워크 도입을 고려해보면 좋습니다.


// 장바구니에 상품 추가 함수
const addToCart = (product, quantity, buttonElement = null) => {
// 현재 장바구니 데이터 가져오기

Choose a reason for hiding this comment

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

장바구니에 상품을 추가하는 addToCart 함수에서 product.productId를 ID로 사용하고 중복 상품일 경우 수량만 증가하는 처리도 깔끔하게 되어 있습니다. 👍

다만, URL에서 바로 접근하거나 페이지 새로고침 시 장바구니 상태를 복원하는 로직이 별도로 명확히 보이지 않습니다. 이 부분은 initCartStorage 가 초기화에서 어느 정도 담당하지만, 관련 상태와 모듈에서 이 복원 로직이 잘 관리되는지 확인해보면 좋겠습니다.

</div>
<!-- 수량 선택 및 액션 -->
<div class="border-t border-gray-200 p-4">
${QuantitySelector({

Choose a reason for hiding this comment

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

상품 상세 페이지에서 수량 선택시 QuantitySelector를 통해 이벤트 리스너를 설정한 점과 장바구니 담기 이벤트 후 수량을 1로 초기화하는 처리 모두 명확해서 유지보수에 좋습니다. 👍

다만, 상세 페이지 내 관련 상품 렌더링 부분은 API 요청 후 바로 내부에서 렌더링하므로, API 실패 시 사용자 피드백(에러 메시지)을 친절히 보여주는 부분이 잘 되어 있습니다. 이런 예외처리는 실무에서 자주 발생하니 좋은 접근입니다.

}

// 상품 목록으로 돌아가기 버튼
const goToProductListBtn = event.target.closest(".go-to-product-list");

Choose a reason for hiding this comment

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

SPA 네비게이션을 위해 이벤트 위임으로 상품 목록, 상세, 브레드크럼, 목록 복귀 버튼 등에 모두 라우터 네비게이션 연결한 점이 적절합니다. 👍 여기서 한 가지 제안 드리자면,

현재 여러 조건문으로 분기하는데, "클릭된 요소가 product-card, product-info, product-image 등인지"를 함수로 추출해 재사용 가능하게 하면 가독성이 더 좋아질 수 있습니다.

예:

const getProductIdFromEvent = (event) => {
  const selectors = ['.product-card', '.product-info', '.product-image', '.related-product-card'];
  for (const selector of selectors) {
    const el = event.target.closest(selector);
    if (el) return el.getAttribute('data-product-id');
  }
  return null;
};

그리고 나서 이 함수로 id 획득 후 네비게이션하면 중복 코드 감소에 효과적입니다.

updateQuantity(value);
});

quantityInput.addEventListener("input", (e) => {

Choose a reason for hiding this comment

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

QuantitySelector 컴포넌트와 그 이벤트 초기화 함수가 매우 명확하게 분리되어 있고, 여러 옵션(max, onQuantityChange, size)을 추가해 유연하게 만든 점이 좋네요. 👍

한 가지 개선으로, 이벤트 리스너 등록 시, 이벤트 핸들러 내부에서 상태 업데이트와 DOM 조작이 합쳐지지 않고 분리되도록 리팩토링하면 테스트 용이성과 유지보수성 측면에서 더욱 좋아질 수 있습니다.


return {
route,
params,

Choose a reason for hiding this comment

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

Router 클래스에서 경로를 정규식으로 변환하는 로직과 라우트 매칭 구현은 직접 구현하기 까다로운 부분인데, 체계적으로 잘 설계하셨습니다. 👍

다만, 이 과정에서 라우트 파라미터 키 배열(keys)이 있는데, 이 값이 match의 결과를 파싱할 때 실제 할당되고 있는지 조금 더 명확한 리턴 형태와 테스트를 통해 확인해보면 좋겠습니다.

또한, 현재 정규식 생성 객체를 { regex, keys } 형태로 반환하고 있는데, 변수명이 중복되어 혼란 가능성이 있어 regexObj 등 별도 변수명 사용을 추천드립니다.


// 개수 변경 이벤트
const limitSelect = document.getElementById("limit-select");
if (limitSelect) {

Choose a reason for hiding this comment

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

카테고리 선택 관련 UI 렌더링과 이벤트 위임 처리를 한 곳에서 집중 관리하여 관심사 분리가 어느 정도 이루어진 점이 좋습니다. 👍

추가로, 2depth 카테고리를 선택할 때 기존에 선택된 1depth 카테고리가 복구되거나 상태가 헷갈릴 수도 있으니, 상태 변경과 UI 업데이트 로직 간의 의존 관계를 명확히 정의하고 코드를 주석으로 보완하면 더욱 이해하기 쉬울 것 같아요.

Copy link

@devchaeyoung devchaeyoung 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은 상품 목록, 상세 페이지, 장바구니 UI 및 상태 관리, SPA 라우팅 등 요구사항 전반을 잘 반영하여 구성된 점이 인상적입니다. 특히 무한 스크롤 구현과 장바구니에서 상태 업데이트 이벤트 활용, 토스트 메시지 등 사용자 경험 측면도 충실히 다뤄졌습니다.👍

추가질문에서 민지님은 과제 회고 및 개선점에 대한 고민을 주셨는데, 핵심적으로 코드를 모듈화하고 관심사 분리를 더 명확히 하는 방향으로 나아가면 좋겠다는 내용으로 요약할 수 있습니다. 현재 상태에서도 훌륭하지만, 상태 관리와 이벤트 로직을 각각 별도의 레이어 또는 서비스로 분리하면 유지보수성과 테스트 용이성이 개선됩니다.

전반적인 구조는 컴포넌트 단위로 분리되어 있으나, 상태 관리가 로컬스토리지 의존과 UI 이벤트에 꽤 결합된 편이어서 나중에 요구사항 변화시 유연성이 떨어질 수 있음을 참고해 주세요.

질문에대한 답변

1. 질문 요약

민지님께서는 과제 셀프회고 및 개선점에 대해 구체적으로 고민하고 계시며, 기술적 성장, 특히 코드 설계와 구조 개선에 대한 피드백을 요청하셨습니다.

2. 현재 선택의 장단점

  • 현재 프로젝트는 모듈별 파일 분리를 통해 기본적인 컴포넌트 책임 분리와 SPA 라우팅, 상태 관리를 구현하셨고, 기능별로 이벤트 위임과 커스텀 이벤트를 활용해 동적 반응을 구현한 점이 좋습니다.
  • 반면, 상태 관리(로컬스토리지 사용) 및 UI 렌더링과 이벤트 처리 로직이 컴포넌트 내에 분산되어 있어 관심사 분리 관점에서 다소 복잡해질 수 있습니다.
  • 특히, 장바구니와 상품 목록 페이지에서 상태 업데이트, 렌더링, 이벤트 핸들링이 한 장소에 밀집되어 유지보수와 테스트가 어렵다고 느껴질 수 있습니다.

3. 실무에서라면 이렇게 설계할 것 같아요

  • 상태 관리 분리: 애플리케이션의 상태(장바구니, 상품 목록 필터 등)를 전용 상태 관리 모듈 또는 서비스 계층으로 독립시키고, UI는 이 상태를 구독하며 렌더링하는 방식으로 설계합니다. 예를 들어, 옵저버 패턴을 적용하거나, 간단한 Pub/Sub 이벤트 시스템으로 상태 변화를 알립니다.

  • UI와 비즈니스 로직 분리: UI 컴포넌트는 상태를 표현하고 이벤트를 UI 이벤트로 변환하는 역할에 집중하고, 각종 데이터 처리, API 호출, 상태 업데이트 로직은 별도의 유틸 함수나 서비스에 위임합니다.

  • 라우터와 상태 간 명확한 경계: URL 쿼리 파라미터와 상태를 잘 싱크할 때는 라우터가 상태 복원 및 업데이트 시점과 역할을 정확히 나눠 관리합니다.

  • 코드 예시: 상태 관리 단순화

// store.js
const state = {
  cart: { items: [] },
  productList: { filters: {}, products: [] },
};
const subscribers = [];

export function subscribe(fn) {
  subscribers.push(fn);
}
export function setState(path, value) {
  // 간단한 경로 변경
  state[path] = value;
  subscribers.forEach((fn) => fn(state));
}
export function getState() {
  return state;
}
// UI 컴포넌트에서는
subscribe((state) => {
  renderCart(state.cart);
  renderProductList(state.productList);
});

4. 앞으로 구조를 잡을 때 참고하면 좋은 포인트

  • 관심사 분리(상태/로직/렌더링)
  • 이벤트 위임과 커스텀 이벤트는 훌륭하지만, 이벤트 핸들러가 비대해지는 것 주의
  • 라우터와 상태 연동 시 URL 쿼리 관리 집중 및 일관성
  • 상수(예: 정렬 옵션, 카테고리 등) 선언 위치 및 재사용성 확보

민지님, 이번 과제에서 여러 복잡한 요구사항을 체계적으로 풀어낸 모습이 좋습니다. 앞으로 단계적으로 상태 관리 및 모듈 분리 연습을 통해 설계 능력을 더 키워나가면 더욱 탄탄한 SPA를 만드실 수 있을 거에요. 궁금한 점 언제든지 편하게 물어봐 주세요!


const itemSkeleton = () => {
return /*html*/ `
<div class="bg-white rounded-lg shadow-sm border border-gray-200 overflow-hidden animate-pulse">

Choose a reason for hiding this comment

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

민지님, 현재 상품 목록 페이지에서는 무한 스크롤을 IntersectionObserver로 구현하여 스크롤이 하단에 가까워지면 자동으로 다음 페이지를 로드하도록 설계하셨네요. 이 부분은 요구사항에 부합하는 좋은 접근입니다.👍

다만, '홈 페이지에서만 무한 스크롤이 활성화된다'는 조건이 명시되어 있는데, 현재 코드와 구조 상 무한 스크롤이 특정 페이지에만 한정되는 로직이 라우터나 페이지별 분리에서 명확히 보이지 않습니다. 무한 스크롤과 페이지네이션 로직이 공유된다면, 페이지별 활성화 여부를 컨트롤할 수 있도록 깔끔하게 분리해보면 좋겠습니다.

예를 들어, 다음과 같이 조건부로 무한 스크롤 관찰자를 등록하는 방법을 고민해볼 수 있습니다:

if (enableInfiniteScroll) {
  scrollObserver.observe(statusContainer);
} else {
  scrollObserver.disconnect();
}

이렇게 하면 향후 다른 페이지나 상세페이지에서도 무한 스크롤 사용 여부를 쉽게 조절할 수 있어 유지보수에 도움이 됩니다.

};
localStorage.setItem("shopping_cart", JSON.stringify(initialCart));
return initialCart;
}

Choose a reason for hiding this comment

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

장바구니 데이터를 로컬스토리지에 직접 읽고 쓰는 로직이 잘 정리되어 있네요! 👍

특히 cartUpdated 커스텀 이벤트를 활용하여 같은 탭 내 다른 컴포넌트도 상태 변경을 인지할 수 있도록 설계한 점은 확장성과 반응성을 고려한 좋은 선택입니다.

다만, 장바구니 데이터 구조가 단순 배열에 focus 되어 있는데, 향후 예를 들어, 사용자별 장바구니라면 분리해야 하는 케이스가 있습니다. 따라서 "상태 관리" 또는 "데이터 저장소" 역할을 별도 유틸이나 서비스 계층으로 분리해보시면 더 좋겠습니다.

window.updateCartContent();
overlay.classList.remove("hidden");
// body 스크롤 방지
document.body.style.overflow = "hidden";

Choose a reason for hiding this comment

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

장바구니 모달의 이벤트 위임 처리 방식이 잘 되어 있습니다. 👍

삭제, 수량 변경, 선택 삭제, 전체 비우기 등 각 버튼과 체크박스 이벤트를 overlay 요소에 위임하여 관리하는 전략은 효율적입니다.

다만 클릭 이벤트 내부에서 여러 조건문으로 긴 로직이 만들어지는 것은 가독성을 약간 저하시킬 수 있습니다. 만약 관리할 이벤트 종류가 더 늘어난다면, 이벤트 대상을 별도 핸들러 함수로 분리하는 구조를 도입해보면 좋습니다. 예를 들어:

const handleCartOverlayClick = (e) => {
  if (isSelectAllCheckbox(e.target)) return handleSelectAll(e);
  if (isRemoveSelectedBtn(e.target)) return handleRemoveSelected(e);
  // ... 기타 이벤트 핸들러
};

modalOverlay.addEventListener('click', handleCartOverlayClick);

이 방법이 유지보수를 더 쉽게 해줄 수 있습니다.

<button ${idAttr} class="add-to-cart-btn w-full bg-blue-600 text-white ${buttonClass} rounded-md
hover:bg-blue-700 transition-colors" data-product-id="${product.productId}" data-quantity="${quantity}" data-product="${productData}">
장바구니 담기
</button>

Choose a reason for hiding this comment

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

장바구니 담기 버튼 클릭 시 장바구니 데이터 업데이트와 동시에 Toast 알림을 띄우는 부분이 깔끔하게 작성되어 있네요! 👍

특히 여러 탭 내 장바구니 상태 동기화를 위해 cartUpdated 이벤트를 디스패치하는 점도 실무에서 유용할 패턴입니다.

한 가지 더 고려해볼 점은 addToCart 함수에서 product.lprice 또는 product.price를 기본 가격으로 가져오는 데, API의 데이터 통일성이 보장되지 않는 상황에서는 가격 필드를 상수로 관리하거나 타입 체크를 강화하여 런타임 오류를 예방하는 것도 좋습니다.

</div>
<span class="ml-2 text-sm text-gray-600">${(product.rating || 0).toFixed(1)} (${product.reviewCount || 0}개 리뷰)</span>
</div>
<!-- 가격 -->

Choose a reason for hiding this comment

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

상품 상세 페이지 내 별점 렌더링 로직이 별도의 함수로 분리되어 있고, SVG로 별을 직접 그리는 세밀한 표현까지 추가되어 있어 좋습니다.

다만 별점 계산 시 반올림과 half-star 처리 부분이 아주 단순한데, 이 부분을 좀 더 명확히 하거나 컴포넌트화할 수도 있습니다. React 같은 프레임워크에서는 재사용 가능한 별점 컴포넌트로 추출하면 좋겠지만, 현재 vanilla JS에서는 이렇게 함수 분리가 충분히 효과적입니다.

이 부분은 앞으로 컴포넌트화 학습 시 좋은 참고가 될 수 있겠습니다.

if (isAppend) {
// 기존 목록에 추가
allProducts = [...allProducts, ...newProducts];
} else {

Choose a reason for hiding this comment

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

카테고리 선택과 브레드크럼 연동 부분 이벤트 위임 처리도 꼼꼼하게 구현해 주셨네요.

특히, 2단계 카테고리 구조에 맞춰서 1depth, 2depth 선택 시 상태관리와 UI 업데이트가 자연스럽게 이어지는 점👍 입니다.

추가적으로 카테고리가 많아질 경우 렌더링 비용이 커질 수 있어, 필요에 따라 가상 스크롤 또는 페이징으로 나누는 것도 확장 시 고려해보시면 좋겠습니다.

const url = to.pathname + to.search + to.hash;
if (replace) {
window.history.replaceState({ path: url }, "", url);
} else {

Choose a reason for hiding this comment

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

커스텀 라우터 구현은 기본적인 기능을 충분히 커버하며 잘 작성되어 있습니다.

특히 pushStatereplaceState를 사용해 SPA 네비게이션을 구성하고, popstate 이벤트로 브라우저 뒤로가기/앞으로가기까지 지원하는 점이 요구사항에 부합합니다.👍

다만, 경로 정규 표현식 변환에서 복잡한 라우팅이 더 늘어날 경우, path-to-regexp 같은 라이브러리를 사용하는 것도 좋은 선택입니다. 유지보수와 확장성 측면에서 안정적일 수 있기 때문입니다.

const productInfo = event.target.closest(".product-info");
const productImage = event.target.closest(".product-image");
const relatedProductCard = event.target.closest(".related-product-card");

Choose a reason for hiding this comment

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

상품 리스트 및 상세 페이지에서 상품 클릭 이벤트 위임을 통해 경로 이동을 처리하는 방식이 적절합니다.

SPA의 요구사항 중 '페이지 간 이동에서 새로고침이 발생하지 않아야 한다'를 잘 반영했다고 볼 수 있습니다.

추가로, 이 이벤트 핸들러가 페이지 내 모든 클릭 이벤트를 한 번에 다루고 있는데, 이벤트가 너무 많아질 경우 성능이나 가독성 저하가 발생할 수 있으니 규모가 커지면 분리 및 최적화를 고민해보시면 좋을 것 같아요.

}, 300);
}
}, 3000);

Choose a reason for hiding this comment

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

토스트 메시지 컴포넌트 및 표시 함수가 직관적이고 재사용 가능하도록 잘 작성되어 있습니다.

특히 자동 사라짐과 닫기 버튼 처리가 적절하며, CSS 애니메이션을 사용해 부드러운 인터랙션을 구현하신 점👍 좋습니다.

앞으로 로직이 복잡해지면 토스트 메시지 관리(동시 표시, 큐잉 등)를 별도 관리하는 방식으로 확장해보면 좋겠습니다.

>
<svg class="${iconSize}" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20 12H4"></path>
</svg>

Choose a reason for hiding this comment

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

수량 선택 컴포넌트를 재사용 가능하게 잘 분리하신 점이 돋보입니다.

특히 옵션에 따라 크기 조절, ID 프리픽스 등 다양한 커스터마이징을 지원하는 구조가 유연합니다.

수량 변경 시 커스텀 이벤트 발생 및 콜백 지원으로 상태 연동이 편리하게 설계되어 있어서, 장바구니 뿐 아니라 다양한 형태에 적용하기 좋아 보입니다.

미묘하지만, input 이벤트에서 max 값 처리 시 조금 더 명확하게 max가 있는지 여부를 체크하는 형태도 고려해보면 좋을 것 같아요.

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