yhun940731 님의 상세페이지[7팀 최용훈] Chapter 1-1. 프레임워크 없이 SPA 만들기

과제 체크포인트

배포 링크

https://yhun940731.github.io/front_6th_chapter1-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의 소중함을 알았다. 특히 반복되는 DOM 탐색, 이벤트 바인딩 등에서 혼잡해지는 코드로 정신이 아득해졌다.

다시는 리엑트를 미워하지 않기로 다짐했습니다.

기술적 성장

  • 새로 학습한 개념
  1. URLSearchParams 객체에 문자열이 프로퍼티로 들어가면 자동으로 encode를 진행한다.
  2. data attr에서 어트리뷰트 이름이 '-'로 이어진 경우 카멜케이스로 js에서 접근 가능하다. ex. dataset.productId

자랑하고 싶은 코드

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

  • 전체적으로 디렉토리 구조가 혼잡합니다.
  • 촉박하게 코딩하다 보니 변수, 함수 이름이 통일되지 못합니다.
  • 반복되는 코드가 존재합니다.

학습 효과 분석

  • 바닐라로 SPA를 구현하면서 직접 라우트를 개발해보고 동작 원리에 대해 생각해볼 기회가 됐습니다.

과제 피드백

  • 페이지 이동 간, api pending 등 로딩 fallback이 마땅하지 않아 지연되는 느낌이 들었습니다.

AI 활용 경험 공유하기

Copilot을 활용하여 단순 구현하는 기능에 도움을 받았습니다. 확실히 간단한 에러 원인 파악과 단순 코드 구현 시간 단축에 큰 도움이 됐습니다.

리뷰 받고 싶은 내용

  • 간만에 바닐라로 코드를 작성하다 보니, 디렉토리 구성과 함수 분리에 시간 투자를 하지 못했습니다.

  • 전반적으로 시간에 쫓기면서 기능 구현을 하다보니, 스스로의 코드를 찾아서 수정하는 것 조차 버겁다는 느낌이 들었습니다.. 앞으로 초기 프로젝트 설계 관점에서 기본적으로 확립할 방향과 리팩토링 진행 시, 최소한의 방향성에 대하여 피드백 부탁드립니다...!

  • 이벤트 분리와 등록/해제 시점에 대해 피드백 부탁드립니다. 렌더 함수가 돌때마다 렌더 함수의 시작, 끝에 해제/등록을 진행헀지만, 핸들러 함수가 많아질 수록, 혼란스러움이 커지는 것 같습니다...

과제 피드백

안녕하세요 최용훈님, 과제 수고하셨습니다. 이번 과제는 프레임워크 없이 순수 JavaScript로 복잡한 SPA를 구현하면서, 현대 프레임워크들이 해결하는 문제들을 직접 경험해보는 것이 목표였습니다.

각 파일들이 너무 커지지 않도록 역할별로 책임을 나누고 폴더와 파일을 분리를 열심히 나눠준 부분은 참 잘했습니다. 하나의 파일이 너무 커지고 책임이 많아진다면 관리를 하기 어려울테니까요.

현재 코드에서는 안 써도 될 코드들 가령 render()와 같은 부분들을 자동화 할 수 있도로 코드의 책임을 안쪽으로 위임할 부분과 꼭 적어야하만 하는 상태, 템플릿, 이벤트 등을 분리하는 작업과 현재 역할과 계층으로 분리해둔 것과는 별개로 Product, Main등 Route별로, 화면별로, 데이터 별로도 모아서 관리할 수 있도록 구조를 만들어보는 것까지도 목표로 남은 주차들 잘 진행해보시기 바랍니다.


Q) 간만에 바닐라로 코드를 작성하다 보니, 디렉토리 구성과 함수 분리에 시간 투자를 하지 못했습니다.

전반적으로 시간에 쫓기면서 기능 구현을 하다보니, 스스로의 코드를 찾아서 수정하는 것 조차 버겁다는 느낌이 들었습니다.. 앞으로 초기 프로젝트 설계 관점에서 기본적으로 확립할 방향과 리팩토링 진행 시, 최소한의 방향성에 대하여 피드백 부탁드립니다...!

=> 우리가 만들고자 하는 과정에 이미 정답지가 있답니다 바로 React죠! 스스로 생각을 해보다가 막히게 되면 한번 그냥 interface나 모양, 구조들을 그냥 한번 따라가보세요. 왜 이렇게 만들었을까? 하면서 그 배경을 따라가다 보면 얻을 수 있는게 많을 거에요. 그간 10년간의 FE들의 시행착오의 집약체이니까요.

=> 하지만 또 VDOM, 이벤트 델리케이트, hook은 상당히 추상화 기능이 깊은게 있다보니 짧은 시간 SPA를 만들어 보기에 이걸 다 만들어내기는 부담스러울수도 있죠. 그럴때는 Vue등에서 사용되는 Signal을 참고해보는 것도 좋겠습니다. React가 아닌 프레임워크는 전부 Signal을 기반으로 하고 있으니까요.

=> 나머지 Router나 기타 형태들은 npm에서 가장 많이 쓰이는 라이브러리에서 특히나 사용자에게 제공하는 interface를 유지하면서 이걸 어떻게 만들 수 있을까? 이렇게 한번 생각해보시면 좋겠습니다.

Q) 이벤트 분리와 등록/해제 시점에 대해 피드백 부탁드립니다. 렌더 함수가 돌때마다 렌더 함수의 시작, 끝에 해제/등록을 진행헀지만, 핸들러 함수가 많아질 수록, 혼란스러움이 커지는 것 같습니다...

=> 이렇게 열고 닫고 하는 식의 것들이 참 많죠. 이런한 경우에는 cleanup 함수로 반환하는 방식을 자주 사용하곤 합니다. useEffect를 한번 떠올려 보세요.

function useEventListener(target, fn, options) { target.addEventListener(fn, options) return () => target.removeEventListenr(fn, options) }

와 같이 만들어 둔다면 기존에 있던 내용이 뭐든지 상관없이 다음과 같이 사용할 수 있습니다.

const cleanup = useEventListener(window, "click", () => {})

cleanup() // 을 호출하면 해제 가능.

그렇다면 이게

const cleanups = [ useEventListener(window, "click", ...) useEventListener(window, "click", ...) useEventListener(window, "click", ...) useEventListener(window, "click", ...) ]

cleanups.forEach(fn => fn()) 이런 방식이거나 심지어 이것 또한 등록하고 해제하는 것을 cleanup 방식으로 만들 수 있겠죠.

위와 같이 해야할 동작을 미리 기술하고 함수로 리턴하여 lazy하게 사용할 수 있도록 만드는 방법들은 js를 잘 쓰는 자주 쓰이는 기법중에 하나니 잘 응용할 수 있으면 좋겠네요.

좋은 경험이 되었기를 바랍니다. 2주차도 화이팅입니다. 수고하셨습니다.