프로젝트의 주제
- 여행 상품 리스트를 보고 장바구니에 저장할 수 있는 사이트 구현
프로젝트의 요구사항
- 목데이터를 사용해서 과제를 진행
- 별도의 디자인 명세는 주어지지 않는다.
- 요구사항을 충족시키는 선에서 지원자의 판단하에 UI를 구성해주면 된다.
- 필수 기술: chakra-ui, (emotion)
- 필수기능에 부합하는 디자인을 chakra-ui를 이용하여 구현해주세요
- chakra-ui로 할 수 없거나 부가적인 스타일링은 emotion을 이용해 주세요
프로젝트 진행 전 신경쓴 점
기술스택 논의
- 프로젝트에 들어가기에 앞서 각각의 기술스택들은 어떤 것들이 있는지
- 그리고 그것들중 왜 이것을 사용했는지를 문서화하여 정리했다.
- 이를 통해 기술을 도입할 때 그리고 사용할 때 중요한 핵심인 “왜 사용하는가?”를 모두가 이해하고 진행하기에 기술스택을 사용함에 있어 어려움이 감소되었다.
- 이 과정에서 기술스택이 꼭 유행하는 것이 아닌 주어진 프로젝트에 맞는 기술스택을 사용해야 한다는 것을 다시금 느꼈다.
요구사항 분석
- 프로젝트에 들어가기에 앞서 요구사항들을 분석했다.
- 무턱대고 개발하는 것이 아닌 모두가 머리를 모아 먼저 로직을 정리해보는 시간을 갖었다.
- 이를 통해 팀원들이 코드리뷰에서 들어가는 시간을 조금이나마 절약할 수 있었다.
프로젝트에서 도입된 주요 기술
컴포넌트 구조 및 폴더 구조
src
┣ 📂components
┃ ┣ 📂common
┃ ┣ 📂OrderSummary
┃ ┣ 📂Reservation
┃ ┗ 📂Travel
┣ 📂constants
┣ 📂hooks
┣ 📂mocks
┣ 📂pages
┣ 📂providers
┃ ┣ 📂Reservation
┃ ┣ 📂Travel
┣ 📂router
┃ ┣ 📂loaders
┣ 📂types
┣ 📂utils
┣ 📜App.tsx
┣ 📜main.tsx
┗ 📜vite-env.d.ts
Design Pattern
- 우선 페이지를 두가지로 나누었다.
- 여행 상품 리스트를 볼 수 있는 Main페이지
- 저장한 여행아이템을 확인할 수 있는 Reservations페이지
- Main페이지는 다시 Filter와 TravelItemBox로 나뉜다.
- Filter는 다이어그램에는 안나왔지만 자식 컴포넌트로 FilterCheckboxGroup을 하나 뒀다.
- FilterCheckboxGroup는 chakra UI로만 구성되어져 있다.
- 이 chakra에서 제공하는 MenuItemOption이 check되면 MenuOptionGroup의 onChange에 바인딩된 함수를 통해 filter를 구현한다.
- TravelItemBox는 TravelItem으로 구성되어지고
- TravelItem은 ReservationButton을 자식으로 갖는다.
- 또한 QuantityButton도 자식으로 갖는데 이 버튼은 장바구니에서도 쓰이므로 공용컴포넌트에 넣어 재사용성을 높인다.
- Reservations페이지는 ReservationBox와 OrderSummary로
- ReservationBox는 ReservationItem으로
- ReservationItem은 QuantityButton과 DeleteButton으로 나뉘는데
- 이때 이 DeleteButton은 나중에 또 쓰일 수 있으므로 common 폴더에 넣어 놓는다.
- QuantityButton은 Main에서 쓰인 바로 그 버튼이다.
- 각 컴포넌트의 구성을 보면 작은 컴포넌트들이 모여 하나의 컴포넌트를 이루며 그 컴포넌트가 더 큰 컴포넌트를 구성해나아가는 것을 볼 수 있다.
hooks
- 비즈니스로직들을 따로 hook으로 빼서 관심사분리를 하였다.
- Providers를 통해 전역적으로 context에 있는 state들을 props drilling 걱정없이 사용할 수 있게 하였다.
다중 필터링 기능 구현
- 장소 필터와 가격 필터가 혼합되어 있는 다중필터링을 구현해야한다.
- 설계의 주안점은 다음과 같다.
- 우선 필터 자체의 기능만을 따로 filter함수로 만들고 utils로 빼놔서 추상화를 해놓는다.
- 이를 통해 관심사를 분리하고 유지보수를 쉽게 한다.
- 필터기능만 있기에 필터가 하나만 있을 때 두 개 있을 때 아예 없을 때 등등의 조건들을 비교적 쉽게 다룰 수 있다.
- filter함수에서 return되는 filteredData가 우리가 렌더링하는 item 목록이다.
- 필터가 더 늘어나는것을 대비하여 전역적으로 필터조건과 그 상태를 다루는 Reducer를 만든다.
- 이를 통해 필터가 더 늘어나더라도 빠르게 확장을 할 수 있다.
Filter
컴포넌트에서 필터링이 된 데이터의 상태를 변경시키고, 이 데이터를 가지고 TravelItemBox
컴포넌트에서 리렌더링을 해야하기 때문에 멀리 떨어진 컴포넌트에서도 상태를 공유할 수 있도록 Context API를 사용한다.
장바구니 기능 - localStorage사용
- 이 부분은 내가 처음에 만들때 실수를 했던 부분인데
- 나의 경우 localstorage를 진짜 저장소처럼 사용하여
- 장바구니를 만들때 +버튼을 누르면 localstorage의 숫자가 올라가고
- 이 localstorage의 데이터를 기반으로 다시 렌더링을 하는 방식을 사용했다.
- 지금 생각하면 왜 그랬는지, 무슨 생각으로 그랬는지 모르겠다..
- 이 장바구니의 localstorage방법은 그냥 저장을 하고 Reservations 최상위 부모페이지에서 한 번만 그냥 함수로 불러들이는 거다.
- 그리고 렌더링할때는 기존의 방식처럼 context를 사용해 전역적으로 수량 상태를 관리하면 된다.
- 이렇게 하면 유저가 새로고침했을 때만 localstorage를 반영하여 값이 계속 저장되게 할 수 있다.
- 물론 context에서 꾸준히 localstorage에서 set하는 함수를 적용해야한다.
- 즉, context와 localstorage를 계속해서 update하되 렌더링은 context에 있는 것을 하고
- useEffect를 이용해 리렌더링시에는 localstorage에 있는 데이터를 context로 업데이트하면 된다.
context API를 이용하여 props drilling 해결
QuantityButton
컴포넌트에서 수량을 변경하는 이벤트가 발생하여 상태를 변경시킬 때, 멀리 떨어져 있는 컴포넌트인 OrderSummary
가 리렌더링 되어야 하기 때문에 Context API를 활용했다.
ReservationProvider
를 구현하였고, Reservations
페이지의 하위 컴포넌트로 state와 dispatch의 스코프를 한정했다.
ReservationItem
컴포넌트에서는 dispatch 함수를 호출하여 수량 변경 버튼을 클릭하면 reducer를 통해 상태를 변경시킬 수 있게 됩니다. 그러면 OrderSummary
컴포넌트가 리렌더링되며 상품의 총 수량과 총 금액을 업데이트 할 수 있다.
끝으로
- 이번 과제를 하면서 필터링이라는 로직에 대해 깊게 생각해보는 경험을 했다.
- 필요에 의해 라이브러리를 써야 하며 그래서 꼭 redux를 사용하지는 않아도 된다는 것을 알았다.
- 컴포넌트 구조를 설계함에 있어 좀 더 꼼꼼한 설계와 더불어 단단한 이해를 통해 구조가 중간에 바뀐다 할지라도 유동적으로 잘 바꿔야 한다는 것을 알았다.