Skip to content

Conversation

@kimzeze
Copy link

@kimzeze kimzeze commented Nov 10, 2025

과제 체크포인트

배포 링크

https://kimzeze.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로 다시 구현한다.
  • 이 과정에서 직접 가공하는 것은 최대한 지양한다.

과제 셀프회고

이번 과제를 통해, 지금까지 개발을 하는 동안 '지식의 폭이 넓어진게 아니라 자주 사용하는 것에 익숙해져있었구나' 라는 생각이 가장 먼저 들었습니다.

React를 사용하면서 useState, useEffect, useRouter를 당연하게 사용했지만, 이것들이 어떻게 동작하는지, 왜 이렇게 설계되었는지는 깊게 생각해본 적이 없었습니다. 프레임워크 없이 직접 구현해보니, 그동안 당연하게 사용했던 기능들이 얼마나 많은 고민과 설계를 거쳐 만들어졌는지 느껴졌습니다.

특히 생명주기 구현을 하면서 무한 루프 버그를 만나게 되어 정말 힘든 시간이었습니다. 리액트의 useEffect 의존성 배열...같은 것들이 왜 필요한건지 이론이 아닌 실제 버그를 통해 배울 수 있었습니다.


기술적 성장

1. Observer 패턴 공부하고 적용하기

Observer 패턴은 이름만 들어봤지 실제로 써본 적은 없었습니다. AI한테 비유로 설명해달라고 했더니 "유튜브 구독"으로 비유해줘서 이해하는데 도움이 되었습니다.

이번 과제에서는 Router랑 Store 둘 다 Observer 패턴으로 만들었습니다. router.subscribe(render)하면 URL 바뀔 때마다 자동으로 render 함수가 호출되고, store.subscribe(render)하면 상태 바뀔 때마다 자동으로 렌더링됩니다.

과제를 통해 observer.js를 만들 때 Set()을 사용하는 것조차 생소하여, 배열과 set으로 했을 때 중복제거에서 차이가 난다고하는 개념도 찾아가면서 했던 기억이 납니다.

// src/core/observer.js
export function createObserver() {
  const subscribers = new Set();

  return {
    subscribe(callback) {
      subscribers.add(callback);
    },
    notify(data) {
      subscribers.forEach((callback) => {
        try {
          callback(data);
        } catch (error) {
          console.error("Observer callback error:", error);
        }
      });
    },
  };
}

React 쓸 때는 상태 바뀌면 자동으로 리렌더링 되는 게 당연했는데, 알고보니 이런 노력을 하고 있었다는 것을 알게 되었습니다.

2. 상태 관리를 어떻게 할까 - Redux vs useReducer

프로젝트 시작할 때 제일 먼저 고민한 게 상태 관리였습니다. React에서는 useState나 useReducer 쓰면 됐는데, 바닐라로 하려니까 막막했습니다.

그래서 useReducer처럼 switch-case로 하는 방식이랑 Redux처럼 action을 함수로 분리하는 방식 둘 중에 고민했습니다.

useReducer 방식:

function reducer(state, action) {
  switch(action.type) {
    case 'SET_PRODUCTS': ...
    case 'ADD_TO_CART': ...
    case 'REMOVE_FROM_CART': ...
  }
}

이건 익숙하긴 한데, 액션이 많아지면 switch문이 엄청 길어질 것 같았습니다.

Redux 방식:

  actions: {
    setProducts(setState, data) { ... },
    addToCart(setState, product) { ... },
    removeFromCart(setState, id) { ... }
  }

고민하다가 Redux 방식을 선택했습니다. 이유는 단순했는데, 나중에 코드 다시 볼 때 찾기 쉬울 것 같아서였습니다.
dispatch({ type: 'setProducts' }) 하면 actions에서 setProducts 함수만 보면 되니까요.

실제로 장바구니 만들 때 addToCart, removeFromCart, updateQuantity, clearCart 이렇게 4개 액션이 필요했는데, 각각 함수로 분리돼있으니까 수정할 때 편했습니다.
지금 생각해보면 actions 객체가 198줄이나 되는데, 이게 맞나 싶기도 합니다. 페이지별로 분리하는 게 나았을까요?

3. Lifecycle 시스템 만들면서 무한 루프를 만나다

React의 useEffect 를 만드는 상상하며... Lifecycle 시스템을 구현했습니다. mount, watch, unmount 훅을 만들었는데, 여기서 큰 버그를 만났습니다.

watch로 query 파라미터 변경을 감지하는데, 콜백 안에서 dispatch를 호출하니까 무한 루프가 발생했었습니다.

// 문제의 코드
watch: [
  {
    target() {
      return router.getCurrentRoute().query;
    },
    callback() {
      loadProducts(); // 이 안에서 dispatch 호출
    },
  },
];

원인을 찾아보니까 oldValues를 업데이트하는 타이밍 문제였습니다. callback 실행 후에 oldValues를 업데이트하니까, callback 안에서 dispatch → render → watch 다시 실행 → 아직 oldValue가 옛날 값 → 변경 감지 → 무한 반복...~!

과제하면서 일단 해결은 callback 실행 전에 oldValues를 먼저 업데이트해서 해결(?)했습니다.

// src/core/Lifecycle.js:50-54
if (JSON.stringify(newValue) !== JSON.stringify(oldValue)) {
  oldValues[key] = newValue; // 먼저 업데이트!
  callback(newValue, oldValue); // 그 다음 callback 실행
}

3. IntersectionObserver로 무한 스크롤 구현

처음에는 scroll 이벤트로 하려다가 IntersectionObserver를 알게 됐습니다. 화면에 특정 요소가 보이면 콜백이 실행되는 방식이라 무한 스크롤에 많이 사용하는 것 같았습니다.

처음 적용했을 때는 문제가 있었습니다. 새 상품이 로드되면 DOM이 다시 그려지는데, 그러면 observer 연결이 끊어졌습니다.
그래서 한 번만 동작하고 끝이었습니다.

제가 생각한 해결 방법은 watch를 사용해서 상품 목록이 업데이트될 때마다 observer를 다시 연결하는 거였습니다.

// src/pages/HomePage.js
watchs: [
  {
    target() {
      return { length: store.getState().home.products.length };
    },
    callback() {
      // 기존 observer 해제
      if (scrollObserver) {
        teardownInfiniteScroll(scrollObserver);
      }
      // 다시 연결
      setTimeout(() => {
        scrollObserver = setupInfiniteScroll("#scroll-trigger", loadMoreProducts);
      }, 100);
    },
  },
];

setTimeout을 100ms 준 이유는 DOM이 완전히 그려진 다음에 observer를 연결하기 위해서입니다. 무한 스크롤은 특히 AI가 많이 도움을 준 것 같습니다.


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

1. main.js의 거대한 이벤트 핸들러 (410줄)

전역 이벤트 위임 패턴을 사용했지만, 하나의 파일에 모든 이벤트 핸들러가 집중되어 있습니다.

// src/main.js:77-343
document.body.addEventListener("click", (e) => {
  // 링크 클릭
  if (e.target.closest('a[href^="/"]')) { ... }

  // 장바구니 아이콘 클릭
  if (e.target.closest("#cart-icon-btn")) { ... }

  // 장바구니 모달 닫기
  if (e.target.closest("#cart-modal-close-btn")) { ... }

  // ...
});

문제점:

  • 한 파일이 너무 많은 책임을 가짐
  • 새로운 기능 추가 시 main.js를 계속 수정해야 함
  • 테스트하기 어려움
  • 코드 가독성 저하

학습 효과 분석

React가 없었더라면 개발을 할 수 있을까

프레임워크 없이 직접 구현해보니, 프레임워크가 해결하는 문제가 무엇인지 명확히 알게 되었습니다.

  • pushState vs replaceState의 차이
  • popstate 이벤트의 동작 방식

IntersectionObserver:

  • threshold 설정과 콜백 타이밍
  • disconnect와 observe의 생명주기
  • DOM 재렌더링 시 재연결 필요성

디자인 패턴의 실전 적용

Observer 패턴:

  • 구독자 관리 (Set으로 중복 방지)
  • 에러 격리 (try-catch)
  • 단방향 데이터 흐름 구현

과제 피드백

AI 활용 경험 공유하기

AI를 활용한 방식

  1. 아키텍처 설계 단계:

    • 초기에 Observer 패턴, Router 시스템 설계 상담
    • 다양한 접근법의 장단점 비교
    • docs/week1 폴더의 문서들을 AI와 함께 작성
  2. 버그 해결 단계:

    • 무한 루프 버그 증상 설명 → 근본 원인 분석 도움
    • 여러 해결 방안 제시 받고, 디버깅
    • 최종 코드는 직접 작성하고 테스트
  3. 문서화:

    • 구현 후 구현했던 생각들을 문서로 작성 시 정리 도움
    • 코드에 주석을 어떻게 작성할지 가이드

AI 활용의 효과

  • 막혔을 때 빠르게 방향성 제시 (예: "IntersectionObserver를 사용하는 건 어때?")
  • 문서 작성 시 구조화 도움 (예시, 비유 제시)

AI 없이는 어려웠을 부분

  • 정규식 동적 생성: /product/:id/product/([^/]+) 변환 로직
  • IntersectionObserver 재연결: watch 패턴으로 자동화하는 아이디어
  • 초반 프로젝트 시작 방향 Observer + Router + Store + Lifecycle 시스템을 어느정도 구축후 기능 구현했던 점
  • GitHub Pages 배포: BASE_URL 처리, Vite 설정

리뷰 받고 싶은 내용

1. main.js의 이벤트 위임 구조 개선

현재 main.js:77-410에서 전역 이벤트 위임을 사용하고 있습니다.
410줄의 이벤트 핸들러가 한 파일에 모여있는데, 이를 개선하는 방법으로:

  • 도메인별 핸들러 분리 (cartHandlers, productHandlers 등)
  • 각 컴포넌트가 자체 이벤트를 관리하는 방식
    중 어떤 접근이 더 나을까요?
    또한 전역 이벤트 위임의 장점을 유지하면서도 코드 가독성과 유지보수성을 높일 수 있는 패턴이 있을까요?

2. 무한 스크롤의 IntersectionObserver 재연결 패턴

HomePage.js:89-105에서 상품 목록이 업데이트될 때마다 observer를 재연결하고 있습니다.
setTimeout 100ms를 사용하는데, 이게 최선의 방법일까요?
DOM 렌더링 완료를 감지하는 더 명확한 방법이 있는지,
그리고 observer 재연결 패턴의 더 좋은 방식이 있다면 알고 싶습니다!

4팀 코드리뷰

kimzeze and others added 30 commits November 10, 2025 22:31
  - embeddedLanguageFormatting: auto 추가
  - htmlWhitespaceSensitivity: css 추가
  - HTML 템플릿 리터럴 포매팅 개선
  - main.js에 있던 대량의 HTML 템플릿 코드를 template.js로 이동
  - 상품목록, 장바구니, 상세페이지 등 모든 템플릿 포함
  컴포넌트 목록:
  - Header: 헤더 및 장바구니 아이콘
  - Footer: 푸터 영역
  - SearchForm: 검색 및 필터 폼
  - ProductList: 상품 목록 그리드
  페이지 목록:
  - PageLayout: 공통 레이아웃 (Header + Footer)
  - HomePage: 상품 목록 페이지
  - DetailPage: 상품 상세 페이지

  SPA 라우팅을 위한 페이지 단위 분리
  - 템플릿 코드 제거
  - 페이지 및 컴포넌트 import로 대체
  - 바닐라 JavaScript로 SPA 개발을 위한 목표, 아키텍처, 코딩 스타일, 구현 가이드, 빠른 참조 자료 포함
  - 각 문서에서 단계별로 필요한 내용을 정리하여 학습 및 개발에 도움을 주도록 구성
  - 사용자에게 페이지를 찾을 수 없음을 알리는 NotFoundPage 컴포넌트 구현
- 상태 변경 시 자동 알림을 위한 Observer 패턴 구현
- subscribe, unsubscribe, notify 메서드 제공
- Set을 사용하여 중복 구독 방지
- Router와 Store의 기반이 되는 패턴
- Observer 패턴 기반 SPA 라우터 구현
- 동적 라우팅 지원 (/product/:id)
- 쿼리 파라미터 관리 (updateQuery)
- 브라우저 히스토리 API 활용 (pushState, popstate)
- 404 NotFoundPage 컴포넌트 추가
- JSON 직렬화/역직렬화 자동 처리
- 에러 핸들링 포함
- save, load, remove, clear 메서드 제공
- Observer 패턴 기반 전역 상태 관리
- Redux 스타일의 dispatch/actions 구조
- 불변성 유지를 위한 스프레드 연산자 사용
- home, detail, cart 상태 관리
- loading/error 패턴 적용
- localStorage 연동으로 장바구니 영구 저장
- Router와 Store 모두 구독하여 자동 렌더링
- 상태 기반 렌더링으로 리팩토링
- pending → success/error 패턴 적용
- 이벤트 핸들러 통합 (링크, 상품 카드 클릭)
- 상품 개수 표시를 위한 템플릿 문자열에서 중괄호를 달러 기호로 변경하여 올바른 값 출력 보장
- withLifecycle HOC를 통한 컴포넌트 생명주기 관리
- mount 훅 구현 (첫 렌더링 시 1회 실행)
- watch 훅 구현 (반응형 데이터 추적)
- unmount 훅 구현 (컴포넌트 제거 시 정리)
- HomePage와 DetailPage에 lifecycle 적용
- main.js에서 라우트 변경 시 컴포넌트 unmount 처리

바닐라 JS 컴포넌트에 React useEffect와 유사한 기능 제공

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- ProductList에서 pagination prop 받도록 수정
- products.length 대신 pagination.total 사용
- pagination이 없을 경우 products.length로 폴백

현재 페이지 개수(20개) 대신 전체 개수(340개)가 표시되도록 수정

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <[email protected]>
- 검색 입력창에서 Enter 키 입력 시 검색 실행
- router.updateQuery를 통해 URL 쿼리 파라미터 업데이트
- SearchForm에서 현재 검색어 표시
- HomePage의 watch가 쿼리 변경을 감지하여 자동 재로딩
- watch callback 실행 전에 oldValues를 먼저 업데이트
- callback 내부에서 동기적으로 setState 호출 시 재진입 문제 해결
- 동기 dispatch로 인한 render 재실행 시 무한 루프 방지
- store에 categories 상태 추가
- HomePage mount 시 getCategories API 호출
- SearchForm에서 1depth 카테고리 버튼 렌더링
- 카테고리 로딩 완료 시 실제 카테고리 목록 표시
- 페이지당 상품 개수 선택 기능 (10, 20, 50, 100개)
- 정렬 기능 (가격 낮은순/높은순, 이름순/역순)
- SearchForm에서 현재 선택된 값을 드롭다운에 표시
- main.js에 change 이벤트 핸들러 추가
- filters를 router.query에서 가져와 URL과 UI 동기화
- category1 선택 전: 1depth 버튼 표시
- category1 선택 후: 2depth 버튼으로 전환
- breadcrumb 네비게이션 추가
  - 전체 > category1 > category2 구조
  - 현재 위치(category2)는 span으로 표시 (클릭 불가)
- 선택된 category2 하이라이트 표시
- template.js 기준에 맞춰 구현
- category1 클릭: category2 초기화하고 category1 설정
- category2 클릭: category2 설정 (category1 유지)
- breadcrumb 전체 클릭: 모든 카테고리 초기화
- breadcrumb category1 클릭: category2만 초기화
- router.updateQuery로 URL 쿼리 파라미터 업데이트
- ProductItem에 brand 파라미터 추가
- brand가 있으면 표시, 없으면 빈 문자열
- 장바구니 버튼의 data-product-id를 하드코딩된 값에서 실제 productId로 수정
- 버튼의 data-product 속성에 상품 정보를 JSON으로 저장
- id, title, image, lprice, brand 정보 포함
- HTML 속성 내 따옴표 이스케이프 처리
- 상품 목록, 상세 페이지, 관련 상품 모두에서 동일하게 동작
- Header 컴포넌트에 cartCount props 추가
- 장바구니에 상품이 있을 때 빨간 뱃지로 개수 표시
- PageLayout에서 store의 cart 배열 길이를 계산하여 전달
- store 업데이트 시 자동으로 뱃지 갱신
- 토스트 알림 유틸리티 함수 구현
  - success, info, error 3가지 타입 지원
  - 하단 중앙 위치에 표시
  - 3초 후 자동 사라짐
  - 닫기 버튼으로 즉시 닫기 가능
  - fade-out 애니메이션 적용

- 장바구니 담기 이벤트 핸들러 추가
  - data-product 속성에서 상품 정보 파싱
  - store에 addToCart 액션 디스패치
  - 성공 시 토스트 알림 표시
  - stopPropagation으로 상품 카드 클릭 방지
- template.js 기준으로 UI 구조 구현
- 빈 장바구니 / 아이템 있을 때 두 가지 상태 지원
- CartItem 서브 컴포넌트로 아이템 렌더링
- 전체 선택, 수량 조절, 삭제 버튼 UI 포함
- selectedIds로 체크박스 상태 관리
- 총 금액 및 선택 금액 자동 계산
- e2e 테스트를 위한 ID와 클래스명 정확히 적용
- 수량 증가/감소 버튼 기능 추가
- 재고 초과 방지 및 최소 수량(1) 유지
- 선택한 수량만큼 장바구니 담기
- 기존 상품 수량 업데이트 로직 추가
- store addToCart 액션 수정: quantity 파라미터 지원
- 수량 정보 포함한 토스트 메시지 표시
- 같은 category2의 상품들을 관련 상품으로 표시
- 현재 보고 있는 상품은 관련 상품에서 제외
- 관련 상품 클릭 시 해당 상품 상세 페이지로 이동
- 최대 10개까지 표시 (limit: 10)
- 관련 상품이 없으면 섹션 숨김
- store에 setRelatedProducts 액션 추가
- 동적 렌더링으로 하드코딩된 더미 데이터 제거
- 브레드크럼에서 카테고리 클릭 시 홈페이지로 이동
- category1 클릭 시 해당 카테고리로 필터링
- category2 클릭 시 category1 + category2 필터링
- SPA 방식으로 페이지 전환 (새로고침 없음)
- 브라우저 히스토리 지원 (뒤로가기/앞으로가기)
- HomePage의 기존 필터링 로직 재사용
- cart-modal-container를 최상위 컨테이너로 분리
- cart-modal-overlay 클래스로 오버레이 영역 명확화
- 헤더 장바구니 뱃지 공백 제거하여 테스트 통과
- PageLayout 의존성 제거하고 자체 헤더 구현
- 뒤로가기 버튼 및 장바구니 아이콘 포함
- 장바구니 개수 뱃지를 상세 페이지에서도 표시
- Footer 컴포넌트 직접 import하여 사용
- E2E 테스트에서 사용하는 키 이름과 일치시킴
- 장바구니 데이터 저장/로드/업데이트 시 shopping_cart 키 사용
- template.js의 404 UI 구조를 정확히 따름
- SVG 기반 404 아이콘과 메시지 표시
- '홈으로' 링크 제공
- setupInfiniteScroll: 특정 요소가 화면에 보이면 콜백 실행
- teardownInfiniteScroll: observer 정리 함수
- rootMargin 100px로 설정하여 미리 로드
- threshold 0.1로 설정하여 10% 보이면 트리거
- skip 값을 page로 변환하는 로직 추가
- 무한 스크롤을 위해 skip 기반 페이지네이션 지원
- 기존 page 기반 방식과 호환성 유지
- loadMoreProducts 함수로 다음 페이지 상품 로드
- IntersectionObserver로 스크롤 트리거 감지
- 드롭다운 limit 값에 따라 동적으로 로드 개수 조정
- hasNext로 다음 페이지 존재 여부 판단
- 상품 로드 중 "상품을 불러오는 중..." 인디케이터 표시
- 모든 상품 로드 완료 시 "모든 상품을 확인했습니다" 메시지 표시
- watch로 상품 목록 변경 시 observer 자동 재연결
- 닫기 버튼에 id="toast-close-btn" 추가
- e2e 테스트에서 ID로 버튼을 찾을 수 있도록 수정
- PageLayout을 사용하여 일관된 레이아웃 적용
- 헤더와 푸터가 포함된 완전한 페이지 구조
- 토스트가 하나씩만 표시되도록 수정
- 새 토스트 표시 시 기존 토스트 제거
- unmount 메서드 존재 여부 확인 후 호출
- animate-slide-down 클래스 제거 (정의되지 않음)
- window.history.back()을 사용하여 이전 페이지로 이동
- 카테고리, 검색어, 정렬 등 모든 필터 상태 유지
- 히스토리가 없을 경우 홈으로 이동
- 장바구니 모달 열기 시 선택 상태 복원
- 선택 상태 변경 시 자동 저장
- 장바구니 전체 비우기 시 선택 상태 초기화
- 개별 삭제 시 선택 상태에서도 제거
- 새 상품 로드 중일 때 스켈레톤 UI 표시
- 로딩 인디케이터와 함께 제공하여 UX 개선
- 상품 목록과 상세 페이지에서 네트워크 오류 발생 시 사용자에게 에러 메시지 표시
- 재시도 버튼 추가로 사용자가 상품 로드를 다시 시도할 수 있도록 개선
- 뒤로가기 버튼 클릭 시 히스토리에 따라 이전 페이지로 이동하거나 홈으로 리다이렉트
- 이벤트 리스너를 통해 재시도 버튼과 뒤로가기 버튼의 기능 통합
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 제출된 코드는 Vanilla JS로 SPA 핵심 기능들을 체계적으로 구현하여, 상태 관리(store), 라우터, 생명주기 관리, 무한 스크롤, 토스트 메시지, 장바구니 모달 등 주요 컴포넌트와 기능을 분리 및 구조화한 점이 매우 인상적입니다.

특히 withLifecycle 훅과 Observer 패턴 기반 상태 및 라우팅 관리는 확장성과 유지보수를 고려한 설계로 보입니다. 무한 스크롤과 URL 쿼리 동기화, 상세 페이지 라우팅, 그리고 전역 상태에 따른 UI 렌더링 흐름도 적절히 설계되어 있습니다.

다만, 현 시점에서 이벤트 위임이 main.js에 집중되어 있어 코드 규모 증가에 따른 가독성 저하 문제가 예상됩니다. 도메인별 이벤트 핸들러 분리 또는 각 컴포넌트의 독자적 이벤트 관리 전략을 도입하면 개선될 수 있습니다. 또한, 무한 스크롤 IntersectionObserver 재연결 로직에 setTimeout 대신 명확한 렌더링 완료 감지 전략을 적용하는 것도 권장합니다.

대량 HTML 문자열 관리와 UI 상태 갱신 로직 분리, 토스트 메시지 큐 관리 등의 부분도 향후 구조 개선 시 고려할 수 있는 점입니다.

현 구조는 요구사항에 충실하며, 실무에 가까운 SPA 코어 아키텍처를 경험하기 좋은 상태입니다. 이 구조를 기반으로 컴포넌트별 책임과 이벤트 흐름을 더욱 명확히 분리해 나가면 유지보수와 확장성 측면에서 매우 견고한 애플리케이션이 될 것입니다.### 1. main.js 이벤트 위임 구조 개선에 관한 답변

전역 이벤트 위임은 성능적으로 매우 효율적이고 메모리 사용량을 줄이는 장점이 있지만, 이벤트 핸들러 로직이 한 파일이나 한 위치에 너무 몰리면 가독성과 유지보수가 어려워집니다.

  • 도메인별 핸들러 분리
    각 도메인 영역(예: 장바구니, 상품 목록, 상세 페이지)의 이벤트 처리를 별도의 모듈이나 파일로 분리하는 방법입니다.
    이렇게 하면 해당 도메인 코드에 집중할 수 있고, 관련 이벤트 핸들러를 한 곳에서 관리할 수 있어 명확성이 증가합니다.

  • 컴포넌트 자체 이벤트 관리
    각 컴포넌트가 자신의 DOM 이벤트 바인딩과 제거를 담당하는 패턴입니다. 특히 컴포넌트가 마운트/언마운트될 때 이벤트를 바인딩/해제함으로써 이벤트 누수 방지 및 책임 소재가 분명해집니다.

  • 추천 방안
    전역 이벤트 위임의 장점(저렴한 이벤트 수, 동적 요소 대응)을 살리고 싶다면, 단일 글로벌 이벤트 위임을 유지하되,
    이벤트 종류 및 타겟 기준으로 처리 핸들러를 명확하게 분리하는 Dispatcher 패턴을 권장합니다.
    예를 들어 이벤트 유형별 핸들러 맵을 만들어 해당 핸들러 함수들을 도메인별 파일로 분리하고 전역에는 등록과 호출만 담당하도록 할 수 있습니다.

2. 무한 스크롤 IntersectionObserver 재연결 패턴에 관한 답변

setTimeout을 사용해 100ms 딜레이 후 observer를 재연결하는 것보다,

  • requestAnimationFrame 또는 Promise.resolve().then() 등 이벤트 루프 마이크로태스크나 다음 프레임 시점에 observer를 연결하여 DOM 변경 후 바로 실행하는 방법이 더 안정적입니다.

  • 또는 MutationObserver를 활용하여 실제 DOM 변화를 감지한 시점에 observer를 구성할 수도 있지만, 여기서는 비용과 복잡성이 높을 수 있습니다.

  • withLifecycle 훅과 같은 렌더링 라이프사이클 훅 시스템을 활용해 렌더링 함수 끝에 observer 재연결을 넣는 것도 좋습니다.

이 방법들은 명확한 렌더링 완료 신호를 이용하므로 setTimeout보다 안정적이며, 렌더링 지연이나 상황에 흔들리지 않고 정확한 시점에 옵저버를 재설정할 수 있습니다.


종합적으로 현 프로젝트 구조는 학습과 프로덕션 SPA의 핵심 개념 구현에 아주 좋은 시작점입니다. 앞으로도 이벤트 위임 패턴과 비동기 렌더링 처리에서 소개한 패턴을 단계적으로 도입해 확장성 좋은 구조를 완성해보시길 추천드립니다.

</div>
`;
// 전역 이벤트 핸들러
document.body.addEventListener("click", (e) => {
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의 77~410줄에 이벤트 위임 핸들러가 전역으로 집중되어 있는데, 이는 코드 가독성과 유지보수성 측면에서 다소 부담이 될 수 있습니다.

  • 한계점
    • 이벤트 종류가 많아질 경우 main.js가 너무 커지고 복잡해짐
    • 관심사가 섞여 코드 이해가 어려워짐
    • 테스트나 리팩토링이 어려워짐

개선 방법 예시

  • 도메인별 이벤트 핸들러 분리 (예: cartEvents.js, productEvents.js 등)

    • 각 파일에서 이벤트 위임을 각각 관리하거나
    • 단일 전역 이벤트 위임은 유지하되, 이벤트 타입 별 처리 로직을 외부 함수나 클래스로 분리
  • 컴포넌트 자체에서 이벤트를 관리하는 패턴

    • 컴포넌트가 생성/소멸될 때 직접 이벤트 바인딩 / 해제
    • SPA의 각 페이지나 모달 단위로 분리하여 코드 집중
  • 전역 이벤트 위임 장점 유지하면서 개선하는 패턴

    • 이벤트 타입/타겟별로 핸들러 매핑 테이블을 만들고, 처리 로직만 외부 모듈에 분리
    • 이벤트 전달 흐름을 중간에 컨트롤하는 Dispatcher 패턴 활용
    • withLifecycle 같은 훅/라이프사이클 시스템 도입으로 등록과 해제를 명확히

이렇게 하면 전역 이벤트 위임의 퍼포먼스 이점과, 코드의 역할 분리 및 가독성 향상을 동시에 얻을 수 있습니다.

},
});

// 더 불러올 상품이 있는지 확인 (hasNext 사용)
Copy link
Contributor

Choose a reason for hiding this comment

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

2. IntersectionObserver 재연결에 setTimeout 사용

현재 상품 목록 업데이트마다 100ms 딜레이를 두고 observer를 재연결하는데, 이는 명확한 DOM 업데이트 완료 시점 감지가 아니어서 불안정할 수 있습니다.

  • 현재 방식 한계
    • 단순 딜레이 사용은 렌더링 지연이나 프레임 드랍 시 오동작 가능
    • 불필요한 재연결 발생 가능성 있음

더 나은 방법

  • MutationObserver를 활용하여 DOM 변경 완료 감지 후 observer 재연결
  • 또는 렌더링 함수에서 requestAnimationFrame이나 Promise.resolve().then()으로 다음 프레임에서 observer 연결
  • withLifecycle과 같은 생명주기 훅에서 컴포넌트 렌더링 후 연계

예시 코드 변경:

// 렌더링 후 다음 프레임에 observer 설정
requestAnimationFrame(() => {
  scrollObserver = setupInfiniteScroll("#scroll-trigger", loadMoreProducts);
});

이렇게 하면 DOM 변경 완료 직후 안정적이고 정확하게 observer를 연결할 수 있어서, 불필요한 레이아웃 계산이나 플리커링을 줄일 수 있습니다.

url: `${import.meta.env.BASE_URL}mockServiceWorker.js`,
},
onUnhandledRequest: "bypass",
}),
Copy link
Contributor

Choose a reason for hiding this comment

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

3. 렌더링 시 현재 컴포넌트만 교체 및 언마운트 호출 구조

현재 render 함수에서 currentComponent와 새 컴포넌트를 비교하여 변경 시 unmount() 호출 후 렌더링을 진행하는 구조입니다.

  • 장점

    • 불필요한 unmount 호출 방지
    • 컴포넌트 교체 시 적절한 정리 수행
  • 개선 제안

    • 컴포넌트 관리가 복잡해질 경우, 컴포넌트별로 mount/unmount 관리 체계 강화
    • 라이프사이클 훅 내 DOM 이벤트 바인딩 및 해제를 명확히 하여 메모리 누수 방지

앱 확장 시 컴포넌트별 라이프사이클 관리가 중요하므로, withLifecycle 활용을 적극 권장합니다.


// ===== 2. 상태 읽기 (GetState) =====
// 현재 상태를 반환
const getState = () => state;
Copy link
Contributor

Choose a reason for hiding this comment

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

4. 상태 불변성 보장 및 옵저버 노티파이

setState 함수에서 상태를 새 객체로 만들어 불변성을 지키고, 옵저버에 변경을 알리는 점은 좋은 구현입니다.

  • 다만 상태가 중첩되면 부분 상태 관리가 어려워질 수 있으므로,
  • 필요 시 immer.js, Immutable.js 등 도구 사용 검토 또는 상태를 더 세분화하는 것도 방법입니다.


/**
* 장바구니 모달 열기
*/
Copy link
Contributor

Choose a reason for hiding this comment

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

5. 선택된 장바구니 아이템 상태 유지

selectedIds를 모달 레벨 상태로 관리하고, localStorage에 저장/복원하는 구조입니다.

  • 이는 새로고침 시 선택 상태 유지에 효과적입니다.
  • 다만 다중 모달 동시 활성화나 상태 동기화 이슈가 증가할 경우 글로벌 상태 관리(store)에 포함하여 관리하는 게 좋습니다.

export const DetailPage = withLifecycle(
{
// 컴포넌트 초기화 시 1번만 실행
mount() {
Copy link
Contributor

Choose a reason for hiding this comment

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

8. URL 파라미터 변경 감지를 통한 데이터 재로딩

withLifecycle의 watch 기능으로 라우트 파라미터 변화를 감지하여 상세 정보를 재로딩하는 패턴은 SPA내 라우팅 대응에 적절합니다.

  • 이 구조는 향후 다른 동적 파라미터 추가에도 쉽게 대응 가능하며, 유지보수성이 높습니다.

</div>
<!-- 상품 정보 -->
<div class="p-3">
<div class="cursor-pointer product-info mb-3">
Copy link
Contributor

Choose a reason for hiding this comment

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

9. 거대한 HTML 템플릿 직접 삽입

template.js에 대량의 HTML 마크업이 문자열 리터럴 형태로 들어가 유지보수와 가독성에 부담이 있습니다.

  • 개선 방안으로 컴포넌트별 템플릿 분리 또는 템플릿 엔진 활용을 고려
  • 재사용 컴포넌트별로 분리해 책임 명확히 하면 확장성 증가

document.body.addEventListener("click", (e) => {
// 링크 클릭
const $link = e.target.closest('a[href^="/"]');
if ($link) {
Copy link
Contributor

Choose a reason for hiding this comment

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

10. UI 상태 갱신 로직 중복 가능성

장바구니 아이콘 뱃지 상태 업데이트 로직이 render 함수 내에 세부 조건문으로 존재합니다.

  • 재사용 가능한 함수로 분리하여 복잡도 감소
  • 렌더링과 상태관리 분리하여 해당 컴포넌트만 별도 갱신하도록 해도 좋습니다.

// 기존 토스트가 있으면 모두 제거
const existingContainer = document.getElementById("toast-container");
if (existingContainer) {
existingContainer.remove();
Copy link
Contributor

Choose a reason for hiding this comment

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

11. 토스트 메시지 UI 및 자동 제거

타입별 스타일과 아이콘을 관리하고 3초 후 자동 사라지도록 구현한 점은 사용자 경험 측면에서 훌륭합니다.

  • 다만, 여러 토스트가 동시에 표시될 경우 충돌 방지 및 큐 관리 로직 확장이 가능하도록 설계하면 좋겠습니다.

*/
export function withLifecycle(hooks, renderFn) {
// 내부 상태 (클로저)
let isMounted = false; // mount가 실행되었는지 여부
Copy link
Contributor

Choose a reason for hiding this comment

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

12. 커스텀 생명주기 훅 withLifecycle 구현

React useEffect 유사 기능을 vanilla JS로 구현한 점이 인상적입니다.

  • 컴포넌트별 고립성 및 상태 감시에 용이하고, 복잡한 SPA 구조에서도 일관된 라이프사이클 관리 가능
  • 향후 렌더링 최적화나 비동기 지원 등의 확장 고려 가능

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)를 활용하여 자동으로 생성된 내용입니다.

도현님께서 프레임워크 없이 SPA를 직접 구현하며 React 내부 동작 원리와 상태관리, 생명주기 개념의 중요성을 체감하신 점이 인상적입니다. 👍 이번 PR에서 Observer 패턴을 기반으로 하는 라우터와 상태관리(store)를 잘 구현하였고, 무한 스크롤, 토스트 메시지, 장바구니 모달 등 전반적 요구사항을 탄탄히 충족했습니다.

추가질문에서는 main.js의 긴 전역 이벤트 위임 핸들러 구조 개선 방안과 무한 스크롤에서 IntersectionObserver 재연결 방식의 효율적인 대안에 대해 고민하셨는데, 특히 이벤트 핸들러 분리와 DOM 변경 감지 방법에 관한 문의가 핵심이었습니다.

구조적으로 살펴보면, 현재 관심사 분리가 충분히 적용되어 있다고 보여집니다만, 이벤트 핸들러와 store actions의 크기 분산, 무한 스크롤 DOM 감지 방법은 개선 여지가 있습니다. 앞으로도 컴포넌트, 모듈별 책임 분리와 유틸리티 재사용 관점에서 확장성을 고민해보면 좋겠습니다.

질문에대한 답변

1. 질문 요약

도현님은 main.js 내 400줄 이상 되는 전역 이벤트 위임 핸들러가 가독성과 유지보수에 어려움을 주는 점과, 무한 스크롤에서 IntersectionObserver를 이용한 재연결 시 setTimeout 100ms를 사용하는 방식이 적절한지 여부에 대해 문의 주셨습니다.

2. 현재 선택의 장단점

  • main.js 이벤트 위임

    • 장점: 이벤트 위임으로 DOM 개별 요소에 리스너를 붙이지 않아 성능과 메모리 효율에 유리함
    • 단점: 모든 로직이 한 곳에 몰려 있어 가독성 저하, 수정 시 오류 유발 가능성 및 테스트 어려움
  • 무한 스크롤 observer 재연결

    • 장점: 간단하게 DOM 렌더링 이후 시점에 observer를 재설정 가능
    • 단점: setTimeout 대기 시간 임의 설정, 렌더링 완료 시점을 명확히 알기 어려움, 부정확한 타이밍 문제 발생 가능성

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

  • 이벤트 핸들러 구조 개선

    1. 도메인별 이벤트 핸들러 파일 분리하여 main.js는 핸들러 호출만 담당
    2. 컴포넌트별 자체 이벤트 관리 도입해 이벤트 범위 축소
    3. 이벤트 위임은 유지하되, 핸들러 로직의 응집도 높이기
  • 무한 스크롤 observer 재연결 개선

    1. MutationObserverrequestAnimationFrame을 이용해 렌더링 DOM 변화를 감지하여 observer 재설정
    2. DOM update 완료 후 무한 스크롤 트리거 요소가 확실히 존재할 때 observer 재연결
    3. setTimeout 사용 시는 가능한 최소 시간으로 조절하고 공통 함수로 분리

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

  • 관심사 분리를 계속 의식하며, 한 파일 또는 한 함수가 너무 많은 책임을 지지 않도록 주의
  • store actions나 이벤트 핸들러 등 반복적으로 증가하는 코드들은 도메인별 모듈로 분리
  • DOM 렌더링 및 라이프사이클을 명확히 이해하고, 비동기 처리와 상태 업데이트 순서에 신경쓰며 무한루프 방지
  • 무한 스크롤, 모달 같은 UI 컴포넌트는 DOM 변경과 observer 사용을 고려해 적절한 clean-up과 연결 전략

도현님, 이번 과제를 통해 프레임워크 내부 작동 원리를 깊이 이해하는 좋은 경험을 하셨어요. 앞으로도 이 경험을 바탕으로 점점 더 모듈화, 재사용성 높은 코드를 작성해보시면 좋겠습니다!

</div>
`;
// 전역 이벤트 핸들러
document.body.addEventListener("click", (e) => {

Choose a reason for hiding this comment

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

도현님, main.js에 많은 이벤트 위임 핸들러가 하나의 함수에 집중된 점은 유지보수 측면에서 부담이 될 수 있습니다. 👍 이벤트 위임이라는 좋은 기법을 활용하셨지만, 도메인별로 이벤트 핸들러를 분리하는 방법을 추천드려요.

예를 들어 cart 관련 이벤트는 cartHandlers.js, product 관련 이벤트는 productHandlers.js에 두고 export하며, main.js에서는 이들을 모아 한번에 등록하면 코드를 훨씬 읽기 편하고 관리하기 쉬워집니다.

// cartHandlers.js
export function handleCartClick(e) { ... }

// productHandlers.js
export function handleProductClick(e) { ... }

// main.js
import { handleCartClick } from './cartHandlers.js';
import { handleProductClick } from './productHandlers.js';

document.body.addEventListener('click', (e) => {
  handleCartClick(e);
  handleProductClick(e);
  // 필요시 다른 핸들러도 호출
});

또한 이벤트 위임의 장점을 유지하면서 각 핸들러가 자기 관심사만 다루도록 분리하면, 인지 부하를 낮추고 테스트도 수월해집니다.

이와 함께, 주요 컴포넌트별로 자체적으로 이벤트를 관리하는 방법도 있고, 상황에 따라선 이 방식을 조합하는 하이브리드 형태도 좋습니다. 즉, 전역 이벤트 위임 + 도메인별 핸들러 분리를 고민해보시면 좋겠습니다.

},
});

// 더 불러올 상품이 있는지 확인 (hasNext 사용)

Choose a reason for hiding this comment

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

IntersectionObserver를 상품 목록마다 재연결할 때 setTimeout으로 100ms 대기하는 부분은 DOM 렌더링이 완료될 때까지 기다리는 편법 같은 느낌을 줄 수 있어요.

현재 코드처럼 무한 스크롤 요소가 렌더링되고 나서 observer를 연결하는 것은 맞지만, 100ms는 임의의 대기 시간이므로 너무 길거나 짧을 수 있습니다. 만약 렌더링 타임라인을 더 명확히 하려면, requestAnimationFrame이나 MutationObserver를 써서 DOM 변경 완료를 감지하는 방법이 더 견고할 수 있어요.

예를 들어, MutationObserver를 쓰면 상품 그리드 내부 변화(노드 추가/제거 등)를 감지하여 그 후에 observer를 재설정할 수 있습니다.

const observer = new MutationObserver(() => {
  // 무한 스크롤 observer 재등록
});

observer.observe(document.querySelector('#products-grid'), { childList: true });

또는 requestAnimationFrame 체인을 이용해 렌더링 루프 다음 사이클에 연결할 수도 있습니다.

하지만 현재와 같이 간단히 setTimeout에 적당한 시간을 주는 것은 손쉽고 잘 동작한다면 실무에서 충분히 허용됩니다. 다만 이 부분은 트레이드오프로, 더 정확한 DOM 변경 감지가 필요한 경우 위 방법들을 검토해보시면 좋겠습니다.

@@ -0,0 +1,198 @@
import { createStore } from "../core/store.js";

Choose a reason for hiding this comment

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

store.js에서 Redux 스타일로 각 액션을 함수 단위로 분리한 점은 유지보수와 파악에 유리합니다. 👍 다만 도현님 말씀대로 198줄의 actions 객체는 점차 커지면 관리가 어려울 수 있습니다.

이 경우 리듀서별 또는 도메인별(store 모듈별)로 파일을 분리하고, 여러 store slice를 조합하는 구조로 바꾸면 편리합니다.

예를 들어 homeStore.js, detailStore.js, cartStore.js등으로 나누고 각각 createStore 함수를 통해 생성한 뒤, 프로젝트 규모에 맞춰 adapter 혹은 combine 방식으로 관리 가능합니다.

이전 redux toolkit도 이러한 분리와 유지보수를 돕기 위한 시도라 볼 수 있습니다.

만약 이번 과제 범위에서 분리가 어렵다면, 현재 방식 유지하며 커밋 단위로 의미 있는 리팩토링을 꾸준히 해나가도 좋겠습니다.

const oldStr = JSON.stringify(oldValue);

// 객체를 JSON.stringify로 비교
if (newStr !== oldStr) {

Choose a reason for hiding this comment

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

도현님이 고민했던 무한루프 방지용 oldValues 업데이트 순서 변경은 좋은 선택입니다. 👍

watch 훅에서 상태 변경을 감지할 때, callback 실행 전에 oldValues를 갱신하는 방식은 재진입 문제와 무한 호출을 예방하는 실용적 방안입니다.

다만, 이 부분을 좀 더 엄격하게 관리하려면, callback 내부에서 다시 상태를 바꾸는 일이 많을 경우를 대비해 플래그로 감싸거나 비동기 처리하는 패턴도 있습니다.

예) 플래그 방식

if (!isUpdating && newStr !== oldStr) {
  isUpdating = true;
  oldValues[key] = newValue;
  callback(newValue, oldValue);
  isUpdating = false;
}

이번 과제에서 경험한 이 문제는 React의 useEffect 의존성 배열 관리의 핵심 원리와도 일맥상통합니다. 앞으로도 이런 문제를 만날 때 클로저 상태 관리, 타이밍 컨트롤을 고민해보시면 좋겠습니다.

@@ -0,0 +1,39 @@
/**

Choose a reason for hiding this comment

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

IntersectionObserver를 활용해 무한 스크롤을 구현한 점은 모던하고 효율적인 접근이라 좋습니다. 👍

다만 무한 스크롤은 DOM 업데이트 시 observer가 해제되는 문제가 있기 때문에, HomePage.js에서처럼 상품 목록이 갱신될 때마다 observer를 끊고 다시 연결하는 패턴이 필요합니다.

이 유틸 함수가 관여하는 범위는 observer 생성과 해제이므로, 호출하는 쪽에서 DOM 상태 변화를 인지하고 재연결 관리하는 현재 구조가 합리적입니다.

추가로, 여러 컨테이너가 동적으로 변할 때를 대비해 observe 대상 엘리먼트가 변경됐을 경우에도 자동 재연결 기능을 첨가할 수도 있겠습니다. 이것은 복잡도 증가 요인이라 상황에 맞게 결정하세요.

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