[## 과제 체크포인트
배포 링크
https://angielxx.github.io/front_6th_chapter1-1/
기본과제
상품목록
상품 목록 로딩
- 페이지 접속 시 로딩 상태가 표시된다
- 데이터 로드 완료 후 상품 목록이 렌더링된다
- 로딩 실패 시 에러 상태가 표시된다
- 에러 발생 시 재시도 버튼이 제공된다
상품 목록 조회
- 각 상품의 기본 정보(이미지, 상품명, 가격)가 카드 형태로 표시된다
한 페이지에 보여질 상품 수 선택
- 드롭다운에서 10, 20, 50, 100개 중 선택할 수 있으며 기본 값은 20개 이다.
- 선택 변경 시 즉시 목록에 반영된다
상품 정렬 기능
- 상품을 가격순/인기순으로 오름차순/내림차순 정렬을 할 수 있다.
- 드롭다운을 통해 정렬 기준을 선택할 수 있다
- 정렬 변경 시 즉시 목록에 반영된다
무한 스크롤 페이지네이션
- 페이지 하단 근처 도달 시 다음 페이지 데이터가 자동 로드된다
- 스크롤에 따라 계속해서 새로운 상품들이 목록에 추가된다
- 새 데이터 로드 중일 때 로딩 인디케이터와 스켈레톤 UI가 표시된다
- 홈 페이지에서만 무한 스크롤이 활성화된다
상품을 장바구니에 담기
- 각 상품에 장바구니 추가 버튼이 있다
- 버튼 클릭 시 해당 상품이 장바구니에 추가된다
- 추가 완료 시 사용자에게 알림이 표시된다
상품 검색
- 상품명 기반 검색을 위한 텍스트 입력 필드가 있다
- 검색 버튼 클릭으로 검색이 수행된다
- Enter 키로 검색이 수행된다
- 검색어와 일치하는 상품들만 목록에 표시된다
카테고리 선택
- 사용 가능한 카테고리들을 선택할 수 있는 UI가 제공된다
- 선택된 카테고리에 해당하는 상품들만 표시된다
- 전체 상품 보기로 돌아갈 수 있다
- 2단계 카테고리 구조를 지원한다 (1depth, 2depth)
카테고리 네비게이션
- 현재 선택된 카테고리 경로가 브레드크럼으로 표시된다
- 브레드크럼의 각 단계를 클릭하여 상위 카테고리로 이동할 수 있다
- "전체" > "1depth 카테고리" > "2depth 카테고리" 형태로 표시된다
*전체 브레드크럼 눌렀을 때 API param 초기화시키기
현재 상품 수 표시
- 현재 조건에서 조회된 총 상품 수가 화면에 표시된다
- 검색이나 필터 적용 시 상품 수가 실시간으로 업데이트된다
장바구니
장바구니 모달
- 장바구니 아이콘 클릭 시 모달 형태로 장바구니가 열린다
- 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로 다시 구현한다.
- 이 과정에서 직접 가공하는 것은 최대한 지양한다.
과제 셀프회고
✨ 전반적인 소감
Vanilla Javascript로 React와 유사한 방식으로 SPA를 구현하는 것을 목표로 진행했습니다. React의 진입점, 라우터, 상태 관리, 컴포넌트 아키텍처, 무한 스크롤, 이벤트 핸들링 등 프론트엔드 애플리케이션의 핵심적인 개념들을 직접 구현하며 제 지식의 깊이의 실체와 부족함을 많이 느낄 수 있는 시간이었습니다. 특히, 평소 React 덕분에 얼마나 편하게 개발할 수 있었는지 감사함을 느끼며 React의 내부 구조와 아키텍처에 대해 더 알아가보고 싶은 마음이 생기는 긍정적인 영향이 있었습니다.
내가 머리로 아는 것과 몸으로 느끼는 것은 다르다는 것을 깨달을 수 있는 시간이었습니다!
🚀 기술적 성장
🌱 고민과 깨달음
1. 컴포넌트 생애주기 제어의 어려움
객체 지향 문법으로 컴포넌트 클래스를 만들고, 생애주기를 제어할 수 있는 메서드들을 정의해 오버라이딩하며 사용했습니다. 하지만 직접 컨트롤해보니, 평소에 텍스트로만 학습한 생애주기 흐름을 명확히 이해하지 못하고 있었음을 깨달았습니다. 특히, 렌더링, 이벤트 핸들러 등록뿐만 아니라 파괴 시점의 정리 작업(cleanup) 도 중요하다는 점을 실감했습니다.
2. 컴포넌트 인스턴스 관리
홈 화면에서 생성된 홈 페이지 인스턴스가 상세 페이지로 이동했을 때 제거되지 않아, 전역 상태가 변경될 때 홈 화면이 다시 렌더링되는 문제가 있었습니다. 이 과정을 해결하며 구독 해제, 이벤트 리스너 제거, DOM 클리어 등 메모리 관리의 중요성을 배웠습니다.
3. 과도한 재렌더링
렌더링이 단 한 번만 일어나지 않고, 상태가 바뀔 때마다 불필요하게 여러 컴포넌트가 재렌더링되는 현상을 확인했습니다. 이 경험을 통해 React가 왜 Virtual DOM을 통해 재렌더링을 최소화하려 하는지를 이해했습니다.
Vanilla Javascript로 가상 DOM을 객체 트리 형태로 구현하고 이전 객체와 현재 객체를 비교하여 변경된 부분만 렌더링 메서드를 실행시킬 수 없을까?라는 생각을 했지만 이번 과제에서는 시도해보지 못했습니다. 10주 과정이 끝난 후 추후에는 Virtual DOM을 직접 구현하여 diffing과 최소 렌더링을 시도해보고 싶습니다.
새롭게 배운 것
1. 싱글톤과 옵저버 패턴
전역 상태 스토어를 만들 때 싱글톤 + 옵저버 패턴으로 구현했습니다. 싱글톤 패턴으로 스토어의 유일성을 보장했고, 옵저버 패턴으로 상태 변경 시 구독자들에게 알림을 보내 재렌더링하도록 했습니다. 어떤 경우에 이 패턴들을 활용할 수 있을지 몸소 체감해볼 수 있었습니다.
2. this 바인딩
면접 공부용으로만 알고 있던 this 바인딩의 중요성을, 이벤트 핸들러를 구현하면서 깊이 이해하게 됐습니다. 이벤트 핸들러로 메서드를 넘겼을 때 this가 클래스 인스턴스가 아닌 DOM 요소를 가리켜 상태에 접근하지 못하는 문제를 경험했습니다. 이를 통해 브라우저 이벤트 리스너에서 this가 결정되는 방식과, 왜 화살표 함수나 bind()가 필요한지를 배웠습니다.
자랑하고 싶은 코드
개선이 필요하다고 생각하는 코드
프로젝트 핵심 로직을 담당하는 Component, Router 코드를 각 개념을 심도있게 공부해보고 구조를 개선해보고 싶습니다.
1. Component.js
컴포넌트의 생애주기 메서드가 흐름상 직관적으로 읽히지 않아, React 클래스 컴포넌트의 componentDidMount, componentDidUpdate, componentWillUnmount와 동일한 이름과 순서로 인터페이스를 맞춰 개선하고 싶습니다.
componentDidMount() {
// 렌더링 후에 실행
}
componentDidUpdate(prevState, currentState) {
// 상태가 바뀐 후 실행
}
componentWillUnmount() {
// 파괴될 때 정리 작업
}
2.Router.js
필요에 따라 기능을 계속 붙이다 보니 코드가 방대해지고 핵심이 잘 드러나지 않습니다. "경로 감지 → 컴포넌트 매칭 → 렌더링"이라는 핵심 흐름을 유지하면서 불필요한 로직을 걷어내고 간결하게 리팩터링하고 싶습니다.
학습 효과 분석
-
가장 큰 배움: React의 아키텍처를 직접 구현하면서 내부 동작 원리를 이해했고, 평소 잘 보이지 않던 불편함과 필요성을 직접 체험했습니다.
-
추가 학습이 필요한 영역: Virtual DOM, diffing 알고리즘, 메모리 누수 방지, 가상화된 리스트 렌더링 기법
-
실무 적용 가능성: 컴포넌트의 생애주기 관리, 이벤트 해제, 상태 관리 패턴은 실무에서 바로 적용 가능하다고 생각합니다.
📚 Self Wrap-up
✅ TO STUDY LIST
🌱 React 심화
- React 클래스 컴포넌트 생애주기 메서드의 정확한 동작 흐름과 내부 구현 방식 공부하고 적용해보기
- React의 Virtual DOM과 diffing 알고리즘 원리 공부하고 실제로 구현해보기
- React에서 불필요한 렌더링을 어떻게 방지하고 최적화하고 있는지
🌱 디자인 패턴
- 상태 관리 라이브러리들은 어떤 패턴을 사용하는지
- Redux, Zustand는 어떻게 구현되어있는지
리뷰 받고 싶은 내용
- Component를 언제 어디서 cleanup/destroy 해야할 지 모르겠습니다.
- Store에 사용된 옵저버 패턴과 그 사용 방식이 적절한 지 모르겠습니다. 컴포넌트에서 setup에서 subscribe하고 cleanup시 unsubscribe시키고 있는데, 그게 맞는지
- 라우터 상태값과 메서드들을 편리하게 사용하기 위한 useRouter.js를 만들어 사용했습니다. (hook은 아니지만 hook 폴더에 있음) 좀 더 편리하고 명쾌한 방법이 없을까 고민됩니다. ](https://github.com/hanghae-plus/front_6th_chapter1-1/pull/64)
*****추가된 테스트 코드
상세 페이지 진입 시 로딩 UI가 있기 때문에, 상세 페이지 관련 테스트의 경우 페이지 로딩을 대기하고 테스트를 진행할 수 있도록 아래 헬퍼 코드를 추가하여 사용했습니다.
// ! 상세 페이지 로딩 대기
async waitForProductDetailLoad() {
await this.page.waitForSelector("#detail-header-container", {
timeout: 10000,
});
await this.page.waitForFunction(() => {
const text = document.body.textContent;
return text.includes("상품 상세");
});
}