Magazine K 프로젝트

원영·2022년 7월 31일
0

1. 프로젝트 소개

📰 Magazine K 프로젝트

📍 프로젝트 목적

프론트엔드와 백엔드 개발자가 팀을 이루고 하나의 웹사이트를 선정해 2주라는 기간동안 웹사이트 클론 코딩을 구현한다. 실제 커머스 사이트를 구현함으로써 기획이나 디자인과 같은 시간과 비용을 줄이고 하나의 웹사이트를 제한된 시간내에 완성해 볼 수 있다. 그동안 배웠던 자바스크립트와 리액트를 활용하여 가이드 라인 없이 스스로의 코드로 기능 구현을 해본다. 또한, 백엔드 개발자와 실질적으로 통신해봄으로써 팀원간의 협업능력을 기른다. 실제 프로젝트와 같은 Agile, Srcum 한 프로젝트 구조로 데일리 스탠드업 미팅, 스프린트 미팅등을 참여해보고 협업 툴을 실제로 적용해본다.

❓Magazine B 사이트 선정 이유

Magazine B의 웹사이트는 2주라는 제한된 시간 안에 회원가입, 로그인, 장바구니, 결제, 배송, 찜하기, 리뷰 등 커머스 웹사이트라면 꼭 구현해야하는 기능을 모두 갖추고 있으며, 화려한 CSS 기능이 없는 깔끔하고 모던한 디자인을 갖고 있어 선택하게 되었다.

또한, Magazine B를 좋아하는 한 명의 고객으로써 사이트를 한번 구현해보고 싶은 생각이 있었다. (좋은 카페를 가거나 서점에 가면 항상 매거진B가 있었다. 좋아하고 느낌있는(?) 그런 근본 브랜드들만 소개하는 것을 보고 몇번 구매했었고, 재밌게 읽었던 기억이 있다.)

🖋 Magazine B란

Magazine B는 세계 최초 브랜드 다큐맨터리 매거진으로, 전 세계의 균형 잡힌 브랜드를 한 호에 하나씩 소개하는 광고 없는 월간지다. 브랜드의 숨은 얘기는 물론 감성과 문화까지 담고있어 브랜드에 관심 있는 사람이라면 누구나 쉽게 웹 사이트에 들어와 월간지를 구매할 수 있는 사이트다.

https://magazine-b.co.kr/



2. 팀원소개 및 기간

📆 기간

2022.07.18 ~ 2022.07.29

👨‍👨‍👧 팀원 및 담당 업무

FE 길현민: Login&SignUp, Cart, Order

FE 노정은: Main, Product Detail, Footer, Cart Modal, Review

FE 주원영: Product List, Navigation Bar, Cart Modal, Search

BE 김동규: Product List, Cart, Order

BE 황유정: Login&SignUp, Product Detail, Review



3. 기술 스택 및 협업 툴

🛠 기술스택

Front-end: HTML, SCSS, Javascript, React, React-Router

Back-end: Phython, Django, MySQL

📥 협업툴

1. git hub

팀프로젝트의 가장 기본이고 근간이 되는 깃헙을 사용하였다. 그동안 연습했던 깃 명령어들을 이용해서 각자 브랜치를 관리하였고, 브랜치 마다 PR을 작성해 작업 진행 사항을 명확하게 확인할 수 있었다.

branch는 각자 기능 단위로 생성해 작업하였다. 라벨을 활용하여 현재 브랜치의 상태를 나타내었고, 리뷰가 필요하면 라벨로 요청해 팀원이나 멘토에게 코드 리뷰를 받았다.

2. trello

❓트렐로 사용 이유

업무의 생산성과 효율성을 높이고 더 나은 프로젝트 결과물을 만들어 내기 위해 트렐로 협업툴로써 사용했다.
트렐로에서는 해야 할 과제들과 이미 완료된 과제들, 팀 구성원 별로 맡은 업무들과 현재 진행 중인 업무들, 앞으로 해야 할 일들과 이번 주에 당장 해야 할 일들을 보기 좋게 구별할 수 있다.

📌트렐로 규칙

  • 하나의 티켓은 페이지/기능 단위로 생성했다.
  • 프론트/백엔드가 구분되어 있고 한 티켓안에는 한사람, 한가지 기능만 포함되어 있다.

🗂카테고리

카테고리를 크게 네 가지로 나눴다.

  • Backlog - 앞으로 해야 할 모든 티켓들
  • This Sprint - 이번 스프린트에 해야 할 티켓들
  • In Progress - 현재 개발중인 티켓들
  • In Review - PR 작성 후 리뷰중인 티켓들
  • Done - 완료된 티켓들Backlog: 이번 프로젝트동안 앞으로 해야할 모든 티켓들을

🧐아쉬운 점

트렐로 사용이 처음이다보니, 현업에서 사용하는 것 만큼 효율적이게 사용하지 못한 것 같다. 다음에는 티켓을 좀 더 세분화해서 작성하고 티켓의 이동을 체계적으로 관리하고 싶다.

3. Notion

😎노션 사용

노션에서는 트렐로이외에 팀원끼리 공유할 사항들을 정리해서 기록했다. 예를들어 매일 진행하는 스탠드업 미팅에서의 회의록을 각자 노션에 정리하거나, 자신이 구현한 사항들을 간략하게 작성해 팀원들과 공유했다.

🤔사용 이유

그리고, 노션을 활용하게 된 가장 큰 이유는 2차차 스프린트 때, 팀 회고 시간에서 모든 팀원들에게서 나온 의견이기 때문이다. 트렐로만 사용하기에는 아직 미숙한 부분이 있었고, 아직 서로 어떤 작업을 진행하고 있는지 파악하는 것이 미흡하기 때문에, 또한 API명세서 작성이나 회고록을 작성하기 용이하기 때문에 노션을 활용하자는 의견이 나왔고 바로 2주차부터 작성하기 시작했다.

🙋활용 예시

🧷공유 사항들

🖊회의록

📝기능 구현 설명



4. 🚫 저작권 관련

사이트에서 사용하는 폰트, 아이콘, 사진 등 저작권에 관련된 부분은 사용하지 않도록 주의하며 프로젝트를 진행하였다.

  • 폰트는 맥북 기본 폰트를 사용
  • favicon을 포함한 아이콘은 무료 아이콘 제공 사이트 혹은 아이콘 라이브러리를 활용
  • 프로젝트에 사용된 영상과 사진들은 unplash, pixabay에 등록된 사진들로 대체
  • 데이터는 크롤링을 하지 않고 직접 수집


5. 구현 사항

프로젝트 동안 구현한 사항들을 정리했으며, 내가 맡은 부분은 더 상세하게 기록하였다.

5-1. 회원가입 & 로그인 페이지

회원가입 및 로그인 데이터 유효성 검사.

로그인 시 토큰 발급 후, 로컬 스토리지에 저장


5-2. 메인 페이지

MainSectionMenu 페이지 이동 기능 구현

동영상 자동재생 기능 구현


5-3. 제품 리스트 페이지

카테고리 메뉴 탭 포커스온 기능 구현

  • 각 카테고리 메뉴 탭을 컴포넌트화 시키고 부모 컴포넌트에서 useState값 선언, 이 state를 props로 각 컴포넌트에 넘겨준다.
  • state값(true or false)을 활용하여 메뉴 탭이 포커스 on or off되도록 css를 설정.
  • 한 카테고리 메뉴 탭이 클릭되면 true, 나머지 메뉴 탭들은 false가 되도록 로직 구현.

query parameter를 이용한 카테고리별 데이터 호출

  • 카테고리 메뉴탭을 클릭하면 url에 ?category={number}의 형식으로 나타나게끔 로직을 구현하고
  • searchParams를 이용해 category 값을 받아 API의 endpoint로 요청한다.
  • 백엔드 API는 해당하는 데이터들을 전송해준다.

pagenation 및 sort 기능 구현

  • page당 상품 갯수(limit) 버튼을 추가해 사용자가 값을 입력하면, 이 값을 API endpoint로 전달한다.
  • 이때 API로부터 전체 상품 갯수(total)를 전달받아, total과 limit을 사용해 전체 페이지 수를 구한다.
  • 전체 페이지수를 기준으로 페이지네이션 로직을 구현하고, 페이지 수를 클릭할 때마다 이값도 API에 전달해준다.
  • 백엔드 API는 limit, page, category 값을 받아 해당하는 데이터리스트를 전송한다.
  • sort 버튼을 추가해 사용자가 값을 입력하면 백엔드와 약속한 string을 API에 보내준다. 백엔드 API에서 해당하는 정렬 순서로 데이터리스트를 보내준다.

5-4. 제품 상세 페이지

상세페이지 장바구니 추가 버튼 기능 구현

구매자만 입력 가능한 리뷰 기능 구현


5-5. 네비게이션 바

로그인 토큰 값을 기준으로 메뉴 탭 구성이 변경되는 기능 구현

  • 로그인 시 발급되는 토큰 값을 사용해 삼항 연산자로 메뉴탭이 변경되는 기능 구현

네브바의 search 메뉴 탭을 클릭하면 검색 모달창이 나오는 기능 구현

  • isClicked라는 state값을 선언 후, search 탭이 클릭되면 state값을 ture & false로 바뀌도록 구현
  • isClecked 값을 기준으로 앤드연산자를 활용해 검색창이 조건부 렌더되도록 구현

검색 기능 구현

  • 사용자가 검색창에서 값을 입력하면 그 값을 담은 url로 이동하도록 로직을 구현했다. (navigate(/Search?keyword=${searchValue});)
  • 사용자가 입력한 검색값을 query parameter를 이용해 API에 보내주고, 응답받은 필터된 데이터 리스트를 화면에 구현

장바구니 모달창 css 기능(슬라이드)

  • 네브바의 Cart 탭 클릭 시 모달창이 오른쪽 구석에서 슬라이드 형태로 튀어 나오도록 구현하였다.
  • boolean state를 이용해 className이 다르게 부여되도록 하였고, 이를 이용해 슬라이드 css를 적용하였다. (transition)
  • 또한, 상품리스트페이지와 상세페이지의 장바구니에 추가 버튼을 눌러도 모달창이 작동하도록,
  • 모달창을 조절하는 state값을 라우터에 선언하여 각 컴포넌트에 props로 내려주었다. (이번 프로젝트에서는 별도의 라이브러리를 사용하지 않기로 했기 때문)

장바구니 모달창 기능 구현

  • 모달창에 담긴 상품의 수량이 변경될때마다 API에 해당 상품과 수량에 대한 정보를 보내준다.
  • 각 상품의 총 수량과 총 가격은 API로부터 받아오지 않고 프론트단에서 구현하였다.
  • 자식 컴포넌트의 총 수량과 가격을 계산하기 위해, useState의 set함수를 활용하여 자식에서 부모 컴포넌트로 데이터를 보내주는 방식을 사용하였다.
  • 유저가 장바구니에 담긴 상품을 삭제하면 삭제를 원하는 상품에 대한 정보를 API에 보내주어 데이터베이스에서 삭제하였으며,
  • 프론트단안에서도 해당 상품을 삭제하도록 filter 메소드를 사용하였다.

5-6. 장바구니 메인 & 결제

상품의 수량 변경이나 삭제 시, API에 데이터를 전송하며 변경 사항을 프론트 단에서 구현

장바구니 상품의 전체 선택 혹은 일부 선택 기능

회원에게 주어진 일부 포인트로 결제


5-7. 푸터

웹사이트를 대표하는 정보들을 상수데이터로 표현



6. 성장 포인트

1. 상품 리스트 페이지

1-1. 목데이터가 왜 필요한지, API에서 어떤 데이터들을 받으며 key값들을 어떻게 할 지에 대한 고민.

  • 상품 리스트 페이지를 구성하면서 화면에 보여지는 거의 모든 데이터들은 백엔드에서 가져와야 하며, 어떻게 어떤식으로 가져와서 어떻게 보여줘야할 지에 대한 고민을 많이할 수 있었다.
  • 특히, 이전에 혼자 프로젝트를 진행할 때 막연하게 생각했던 것과 달리, 앞으로 데이터를 가져올 것이라고 생각하고 코드를 구현했다.
  • 백엔드 개발자와 계속 소통해야하는 상황들이 주어졌다. 키를 맞춰나가는 부분, 솔팅 필터링 페이지네이션 등 어느 쪽에서 맡아서 해야할 지 등의 사항들을 계속 소통해나갔다.

1-2. 카테고리 탭을 클릭하였을 때, 해당 카테고리에 해당하는 값만 데이터를 불러오기 위해서 백엔드와 어떻게 통신하고 정해야 할지에 대해 고민

  • 백엔드와 통신하기 전, 카테고리 별 목데이터를 만들어 카테고리 탭을 누르면 해당하는 데이터들이 호출되는 형식으로 먼저 구현하였다.

  • 그러나, 매거진B 홈페이지를 살펴보니 카테고리나 페이지를 누르면 url의 ?뒤에 cate_num와 pg가 변하는 로직을 발견하였고 이는 라우터 돔의 쿼리파라미터 기능이라는 것을 알게되었다..

    ⇒ 백엔드와 카테고리마다 정해진 넘버를 지정하고, 이 넘버를 쿼리 파라미터에 담아 API의 엔드포인트로 요청하기로 하였다.

    ⇒ 이를 위해, 카테고리 메뉴탭을 클릭하면 쿼리 파라미터가 포함된 url로 이동하게 하였고 navigate('?category=${*category*}');

    ⇒ 해당 url로 이동하여 쿼리 파라미터 값을 searchParam를 이용해 변수에 담고, 이 변수를 API 엔드포인트에 넣어줬다.

    ⇒ 결국, 카테고리 별로 다른 엔드포인트를 갖게된다. 백엔드에서는 엔드포인트에 해당하는 데이터리스트를 보내줄 수 있게된다.


1-3. 특정 개수의 상품만 화면에 보여지게 하는 기능에 대한 고민

  • 매거진B 사이트는 상품 리스트를 20개씩 보여주지만, 우리는 첫 미팅때 5개씩 보여주는 것으로 정했다.

  • 현재 모든 데이터에 대해 map() 메서드를 사용하기 때문에, 모든 상품들이 한 페이지에 노출되고 있다.

    ⇒ 결국 특정 개수만 보여주려면 map()메서드를 적용하는 주체. 즉, 배열에 값을 잘라서 써야겠다는 생각을 했다.

    ⇒ 상품 리스트 데이터가 담긴 배열 = productList 에 slice() 메소드를 적용해 5개씩 배열을 잘라줬다.

    ⇒ 최종적으로 데이터가 많아졌을 때도 잘 동작하기 위해서 prodList.slice(offset, limit * page).map 과 같이 구현하였다.

    • limit은 페이지에서 보여줄 상품의 개수이다. 우리는 5개씩 보여줄 예정이므로 5.

    • page는 페이지네이션에서 현재 페이지를 의미한다.

    • offset은 slice()의 시작위치로, (page-1)*limit의 공식(?)으로 산출할 수 있다.

      ⇒ 이렇게 상품리스트 배열을 가공하면 데이터가 몇개가 들어오던지 5개씩 보여주는 구현이 가능하다.


1.4 페이지네이션에 대한 고민

위 과정을 통해 상품을 특정 개수만 화면에 보여지게 구현하였다. 이제 다음페이지로 갔을때 다음 데이터가 불러와지도록 페이지네이션 기능을 구현해야한다.

⇒ 상품 총 갯수를 알아야 몇 개의 페이지가 필요한지 알 수 있고, 그 값을 기준으로 상품 리스트 하단에 페이지네이션을 구현할 수 있다.

⇒ 페이지네이션을 구현할 때, 어떻게 map()메서드로 사용할 수 있을지에 대한 고민이 많았다. 왜냐하면 상품 리스트의 경우 상품에 대한 정보가 담긴 배열에 대해서 map()를 적용하면 되지만 페이지네이션의 경우 map함수를 적용할 대상 배열이 없기 때문,

⇒ 구글링을 통해 알게된 방법으로 배열을 만들고 map함수를 돌려 페이지네이션 기능을 구현할 수 있었다. 그 방법은 다음과 같다.

⇒ Array(pageNum).fill().map()

  • 여기서 pageNum은 계산으로 구한 페이지수이다. 예를 들어 5개씩 보여주는 페이지에서 26개의 상품이 있다면 27/5 = 5.4이고 올림을 통해 6이라는 페이지 수를 얻을 수 있다.
  • Array(6)을 하게되면 길이가 6인 배열이 생성되면 fill()메소드를 통해 값을 undefiend로 넣어준다.
  • 이 상태에서 map함수를 적용하고 그 안에 값으로 페이지네이션에 필요한 JSX를 넣는다.
  • 백엔드 개발자와 협의 후에 프론트 단에서 페이지네이션 하는 것이 아니라 백엔드 단에서 페이지네이션하여 데이터를 보내주는 것으로 결정하였다.

⇒ 그 이유는, 사이트 규모가 커질수록 서버에서 모든 데이터를 보내고 프론트단에서 그 데이터를 가공하는 것은 효율적이지 못하기 때문.


1-5. Query parameters를 이용한 카테고리별 데이터 호출 기능 구현

  • 카테고리 별로 데이터를 요청하기 위해서 fetch함수의 링크에 변수를 줘야한다.

    ⇒ 그 변수를 상품리스트의 Query parameters으로 할당하면 적합하다고 판단했다.

  • 쿼리 파라미터를 사용하기 위해 useSearchParams를 이용했고, 쿼리스트링값(카테고리넘버)를 useEffect안의 fetch함수 url값에 넣었다.

    ⇒ 이때 useEffect는 카테고리가 바뀔때마다 상품 리스트를 새로 가져와야 하므로, 쿼리스트링 값에 변화가 생기면 동작하도록 구현했다.

    ⇒ 카테고리 메뉴 탭을 클릭하면 url의 쿼리파리미터(카테고리넘버)가 변경되고 그 변경된 값에 맞는 데이터를 fetch함수를 통해 가져와 ui에 렌더시킨다.


1-6. 목데이터가 아닌 백엔드 API로부터 데이터를 가져와서 상품리스트를 렌더시키는 기능 구현

목데이터를 프론트 단에서 가공하여 ui에 상품리스트를 보여줬었는데, 실제로 백엔드로부터 데이터를 받아서 보여주는 과정에서 많은 착오가 발생했다.

첫째로는 데이터가 배열자체로 오는 것이 아니라 response라는 객체안에 result라는 키값에 접근해야 배열에 접근할 수 있었다.

.then(result => productList(result)) << 이것을

.then(result => productList(response.result)) << 로 수정하여 해결하였다.

⇒ 즉, json()통해 js 언어로 가공한 응답은 생각했던대로 배열로 오는 것이 아니라 response라는 객체형식으로 오고, result 키 값에 원하는 배열 값이 있었던 것임.

response = {result : [원하는 배열]}

두번째는 백엔드의 상품리스트 데이터가 몇개인지 정보를 받아야 페이지네이션이 가능했으므로, 상품의 총개수 값을 백엔드개발자에게 요청하였다. 그 결과 데이터를 가져와서 가공하는 과정이 필요했다. (상품총개수 데이터를 제외한 나머지 상품정보만 productList state에 추가)

⇒ API로 부터 상품정보를 받아서 map함수를 돌리기 위해서는 배열에 상품 정보 객체만 들어있어야 한다.

⇒ 그러나, 추가적으로 페이지네이션 기능을 구현하기 위해서는 해당 카테고리의 상품수를 미리 알아야하고 이 값은 배열에 같이 담겨져온다.

⇒ 즉, fetch함수의 마지막 부분(.then)단에서 이 배열을 잘라서 나눠놔야 원하는 값을 가져와서 쓸 수 있다.

.then(res => res.json())
.then(res => {
	const prodNum = res.result.length - 1;
	const dataList = res.result.slice(0, prodNum);
	const total = res.result[prodNum].category_total;
	setTotal(total);
	setProdList(dataList);

이후, 코드의 가독성을 증가시키기 위해 백엔드개발자와 데이터 형식에 대한 상의를 하였다. 백엔드측에서 데이터를 가공해서 보내는 과정은 문제되지 않았고, 서로의 코드 가독성을 높일 수 있다는 결론이 나왔다. 결국 백엔드 측에서 데이터를 정리해서 보내주셨고, 위의 코드 처럼 프론트단에서 데이터 가공하는 과정이 없어 가독성이 증가할 수 있었다.

.then(res => res.json())
      .then(res => {
        setTotal(res.result[0].total_count);
        setProdList(res.result[0].products);
      });

1-7. 페이지당 상품 갯수, 솔팅 버튼 구현

기존에는 상품 리스트 페이지에서 데이터를 백엔드에 요청할 때, 카데고리 넘버와 페이지수 2개의 변수를 엔드포인트로 하여 요청하였다. (페이지당 상품 갯수는 5개로 고정)

그러나, 5일차 스탠드업 미팅과정에서 리스트 페이지에서 페이지당 갯수와 솔팅버튼을 추가하자는 결과가 나왔고, 백엔드 개발자와 미팅해 본 결과 엔드포인트를 총 4개로 하여 요청하기로 했다.

⇒ 카테고리넘버, 페이지당 상품갯수(limit), 페이지당 첫 상품의 시작위치(offset), 솔팅기준값(최신순, 가격순 등)

imit버튼과 sort버튼은 select와 option 태그를 사용하였고, 이 값이 변화하면 그 값을 fetch로 백엔드 API에 호출하는 로직을 구현하였다.

⇒ 처음 구현할 때는 값이 바뀌면 url에 추가하고 그 url의 값을 searchparameter로 받아와서 보내는 로직을 생각했다.

⇒ 그러나, 굳이 url에 넘겨서 그 값을 searchparameter로 받는 로직을 구현하지 않고도 구할 수 있는 값이라면 searchParams를 사용하지 않았다. (limit, offset, sort값에 해당)

⇒ category 값만 searchParams를 이용해 fetch 함수의 url에 넣는다.

⇒ 여기서 fetch함수는 4가지 엔드포인드가 변화하면 작동하는 형식으로 구현하였다.

const category = searchParams.get('category');
const getProductList = () => {
    fetch(
      `http://10.58.3.49:8000/products?category=${parseInt(
        category
      )}&offset=${offset}&limit=${limit}&sort_by=${sort}`
    )
      .then(res => res.json())
      .then(res => {
        setTotal(res.result[0].total_count);
        setProdList(res.result[0].products);
      });
  };

1-8. 카테고리 메뉴 클릭 시 포커스온 기능

5개의 카테고리 중, 특정 카테고리를 클릭하면 해당 카테고리를 제외한 나머지 카테고리는 흐려지도록 기능을 구현하고자 했다.

  • 부모 컴포넌트에서 배열 state(isClickedList)를 선언해주고, Array(카테고리갯수)fill(true)를 사용해 배열의 모든 값에 true를 준다.
  • 각 자식 컴포넌트에 이 배열 state와 map함수의 2번째 인자 index를 같이 넘겨준다. 자식요소에서 해당 메뉴가 클릭되면 함수가 작동하게 했고 그 함수는 자신만 false로 하고 나머지는 true값을 준다.
  • 이때 필요한 값이 부모에서 넘겨준 index 값이다. 즉, state 배열을 forEach로 돌려 forEach의 인덱스와 부모로부터 받은 index가 일치하는 값( 즉 자기 자신에 해당)만 true를 줄 수 있다.
  • className에도 앤드연산자를 통해 menuTapOn/menuTapOff 되도록 구현하였다.
const MenuTap = ({ menu, isClickedList, idx }) => {
  const focusOnMenuTap = target => {
    isClickedList.forEach((menu, i) => {
      if (i !== target) {
        isClickedList[i] = false;
      } else {
        isClickedList[i] = true;
      }
    });
  };

  return (
    <div
      className={isClickedList[idx] ? 'menuTapOn' : 'menuTapOff'}
      onClick={() => {
        focusOnMenuTap(idx);
      }}
    >
      {menu.cate_name}
    </div>
  );
};

2. 네비게이션 바

2-1. 장바구니 모달창 슬라이드 기능

네브바에 Cart라는 탭으로 장바구니 기능이 추가되어 있다.

  • Cart를 누르거나 상품리스트페이지에서 AddCart를 누르거나 상세페이지 장바구니담기를 누르면 값이 업데이트 되면서 모달창이 오른쪽으로부터 나온다.

    ⇒ 우선, 모달창이 오른쪽에서 나타나는 기능을 추가하기 위해 카트모달창을 오른쪽 공간에 숨겨뒀다.

    ⇒ cart를 클릭하면 모달창이 나타나게 해야하는데, 이런 애니메이션 기능을 처음 구현해봐서 구글링을 해봤다.

    ⇒ isClicked라는 boolean state값과 함께 앤드연산자로 classname을 부여하는 방식으로 구현하였다.

    <div className={['cartModal', isClickedCart && 'cartModalOn'].join(' ')}>

    ⇒ isclickedCart 값이 false 이면 className은 cartModal false가 되고, true이면 cartModal cartModalOn가 된다.

    ⇒ 아래 Scss 값을 다음과 같이 부여했다.

    .cartModal {
      position: fixed;
      transition: right 0.1s linear;
      right: -22rem;
    
    }
    .cartModalOn {
      right: 0;
    }

    ⇒ 즉, 모달창이 나타나면 right값이 -22rem에서 0으로 0.1s의 시간동안 linear한 transition이 일어난다.

    ⇒ isClickedCart 값은 cart 메뉴탭을 클릭하면 변경되는 로직이다.

2-2. 장바구니 각 상품의 수량 관리 및 총 수량 관리

  • 장바구니 모달창에는 각 상품의 수량을 증가, 감소시킬 수 있다. 각각의 상품을 컴포넌트화하고 그 안에서 state값으로 수량을 관리하면 각각의 수량을 관리할 수 있다.

  • 그러나 총 수량으로 관리하기 위해서는 자식 컴포넌트의 액션이 부모 컴포넌트의 값에 영향을 주도록 로직을 구현해야했다.

    ⇒ 이러한 이유로 부모 컴포넌트에서도 수량을 의미하는 state를 선언해줬고, 수량이 증가/감소 하는 함수도 선언해줬다.

    ⇒ 이 함수를 자식 컴포넌트에게 props로 넘기고 자식 컴포넌트에서 수량을 바꾸면 동작하는 함수에 부모에서 물려받은 함수(부모 컴포넌트에서의 수량이 증가/감소 하는 함수)를 넣어줬다.

    ⇒ 이를 통해 자식 state값과 부모 state값이 동시에 증가, 감소하는 로직을 구현할 수 있었다.

2-3. 장바구니 각 상품의 금액 계산 및 모든 상품의 총액 계산

  • 상품의 수량 값은 자식요소의 -,+ 버튼을 누르면 값이 1씩 변화하므로 단순히 number state값으로 구현이 가능했다.

  • 하지만, 가격의 경우 상품마다의 금액이 다르기 때문에 수량을 관리하는 로직과는 다르게 구현해야 했다.

  • 각 자식 컴포넌트마다 값을 다르게 관리해야 하므로 state를 배열로 관리해야겠다라고 생각했다.

    ⇒ 빈 배열을 만들고, 배열의 길이를 카트에 담긴 상품의 갯수만큼 만들어줬다. 이때, 페이지네이션에서 사용했을 때와 마찬가지로 Array()메소드를 사용했다.

    ⇒ 이 배열을 각각 자식 컴포넌트에 넘기면서 동시에 map함수의 2번째 인자인 index값도 같이 넘겨줬다.

    ⇒ 자식 컴포넌트에서 state[index] 를 통해 state의 배열 중 자신에게 해당하는 요소만 수정할 수 있다.

    ⇒ 즉, 각각의 컴포넌트에서 state[index] 값을 수정하면 빈 배열이었던 state는 각각의 금액으로 구성된 배열이 완성된다.

    ⇒ 이 배열을 부모 컴포넌트에서 useEffect를 통해 총합을 계산하면 카트에 담긴 상품의 총액을 구할 수 있다.

⇒ 자세한 설명은 아래 글을 참고하면 된다. https://www.notion.so/d89aa2af4ad04c5c82a9b45b748f7481

2-4. 토큰이 있을때만 API에 요청

문제 인식

  • 어느정도 기능구현이 완료되었을 쯤, 테스트용 새로운 브랜치를 만들어 각 기능 브랜치들을 통합하였다.

  • 이 테스트 브랜치로 백엔드와 실질적인 통신을 해보려고 했는데, 메인 페이지에 들어서자마자 콘솔에 에러가 찍히면서 화면에 아무것도 나오지 않았다.

    ⇒네비게이션 바의 카트 모달창 컴포넌트에서 발생한 에러였고, undefined 값에 대해 map 메서드를 적용할 수 없다는 에러였다.

원인 파악

map 메서드를 적용할 배열의 값이 왜 undefined인지 로직을 살펴보았다. 메인페이지에 들어서자마자 네브바 컴포넌트가 호출되고 네브바 안의 카트 컴포넌트는 카트에 담긴 데이터리스트를 요청하게 되는데, 사실 백엔드에서는 카트에 담긴 데이터를 보낼 때 토큰이 있는 경우에만 데이터를 보내던 것이었다.

⇒ 즉, API에 호출해서 받은 값은 없는데 이 값을 가지고 map함수를 돌리다가 에러가 난것이었다.

문제 해결

로그인이 되어 있지 않을 때, 즉 로그인 토큰이 발급되어 있지 않을 때는 카트 데이터를 요청하지 않도록 로직을 수정하였다.
⇒ 데이터를 가져오는 함수에 조건문 추가.

const getCartData = () => {
    if (token) {
      fetch('http://10.58.3.49:8000/orders/cart', {
        method: 'GET',
        headers: {
          AUTHORIZATION: token,
        },
      })
        .then(res => res.json())
        .then(res => {
          if (res.message === 'EMPTY CART') {
            return;
          } else {
            setCartData(res.result[0].product);
            let sum = 0;
            res.result[0].product.forEach(product => {
              sum = sum + product.quantity;
            });
            setTotalOrderNum(sum);
          }
        });
    }
  };

2-5. 모달창을 외부 컴포넌트에서 조절하기

처음엔 매거진B 사이트와 같이 add to cart 버튼을 누르면 모달창이 나오고 장바구니에 상품이 추가되어 나오도록 로직을 구현하려 했다.

그러나, 장바구니 모달창은 공통 컴포넌트인 Nav.js에 속해있고 add to cart버튼은 외부 컴포넌트 리스트페이지와 상세페이지 컴포넌트에 속해있다.

const Router = () => {
  return (
    <BrowserRouter>
      <Nav />
      <Routes>
        <Route path="/Products/:product_id" element={<ProductDetail />} />
        <Route path="/ProductList" element={<ProductList />} />
      </Routes>
      <Footer />
    </BrowserRouter>
  );
};

따라서 add to cart 버튼 클릭 시, 모달창을 띄우기 위해서는 외부 컴포넌트간의 데이터 이동이 가능해야했다.

⇒ 이를 구현하기 위해 구글링한 결과 모든 컴포넌트간의 state 공유가 가능한 context API 혹은 react Redux 라이브러리 같은 기능이 있었는데, 중요한건 프로젝트 마감 시간이 얼마 남지 않았고 이번 프로젝트는 라이브러리를 사용하지 않기로 했었다.

결국, add to cart 버튼 클릭 시 모달창은 나오지 않고 장바구니에 추가됐다는 알람만 뜨게 하는 형식으로 방향을 수정했다.

→ 유저는 카트에 상품이 추가됐다는 알람을 확인하고 네브바의 Cart탭을 클릭하면 isClickedCart 라는 state값이 바뀌어 모달창이 나온다.

→ Cart.js의 useEffect는 API로부터 카트 데이터리스트들을 불러오는데, 2번째 인자값으로 isClickedCart 를 추가해 모달창이 작동하면 자동으로 데이터리스트를 업데이트해 원하는 구현이 가능했다.


+추가사항) Router에서 모달창을 조절하는 useState를 선언하고 각 컴포넌트에 props로 내리는 방법을 이용해 원하던 기능을 구현할 수 있었다. Redux 라이브러리를 사용하지 못하는 현상황에서 대처방안으로 사용하였다.

const Router = () => {
  const [modalState, setModalState] = useState(false);
  return (
    <BrowserRouter>
      <Nav modalState={modalState} setModalState={setModalState} />
      <Routes>
        <Route
          path="/Products/:product_id"
          element={<ProductDetail setModalState={setModalState} />}
        />
        <Route
          path="/ProductList"
          element={
            <ProductList
              modalState={modalState}
              setModalState={setModalState}
            />
          }
        />**
      </Routes>
      <Footer />
    </BrowserRouter>
  );
};


7. 회고

7-1. 아쉬웠던 점😢

📤 팀원간의 정보 공유

처음엔 trello와 구두를 이용해서만 정보를 교류하였다. 트렐로의 사용 체계가 잡혀 있지 않아 트렐로를 이용한 작업 진행 사항 공유가 원활하지 못했다. 이런 부분이 아쉬웠고 다음엔 팀원들이 트렐로를 적극적으로 활용하고 트렐로 사용체계가 확실히 잡혀있어 작업사항이 원활하게 업데이트 됐으면 좋겠다.

중간발표 이후, 우리팀이 팀원간의 소통이나 정보공유가 가장 부족했다고 느꼈다. 다른 조들이 노션으로 API명세서나 작업구현한 사항을 트렐로 보다 세세하게 적어 공유하는 부분을 보고 많은 자극을 받았다.

📓미팅 준비

데일리 스탠드업 미팅은 어제한일과 오늘 할일 그리고 블락커를 간단하게 공유하는 시간이긴 하지만, 이것도 사진이나 동영상 같은 시각자료 없이 구두로 진행하다 보니 아쉬운 부분이 있었다. 특히, 2차 스탠드업 미팅에서는 미팅에 대한 기본적인 준비가 매우 미흡했음을 느끼고 반성을 많이 했다.

🦸PM으로서 역할

팀원들이 스프린트 미팅에서 계획한대로 갈 수 있도록 방향성을 제시하거나, 기능 구현 등 어려움을 겪고 있을 때 도움을 준다거나, 스프린트 미팅같은 큰 미팅을 미리 준비하는 등 PM으로서 역할을 제대로 수행하지 못해 아쉽고 반성한다.

너무 부끄럽고 아쉽지만, 이번 프로젝트를 통해 회고하고 피부로 느꼈으니 다음번에는 같은 실수를 반복하지 않는 개발자2가 되겠다.

🔍미흡한 리팩토링

사실, 초반에는 기능 구현을 하기에 급급해 효율적이고 깔끔한 코딩이 아닌 일단 돌아가면돼 라는 식의 코딩이 많았던 것 같다. 분명 나 혼자 구현한 코드에는 문제가 있었을 텐데 자기주도적으로 멘토님께 찾아가 코딩에 대해 리뷰를 받거나 팀원 혹은 다른 동기에게 조언을 구하지 못했다. 솔직하게 프로젝트를 경쟁적으로 바라본 것 같고, 그래서 남들에게 물어보는게 쉽지 않았던 것 같다. 또한 성격 상 부탁을 못하는 부분, 남의 조언을 잘 받아들이는 부분의 성향이 개발자로서 정말 안 좋은것 같다는 생각을 많이 했다.
⇒ 앞으로는 정보를 공유하는 개발자 문화를 인지하고 받아들여 동기들과 적극적인 소통을 해야겠다고 느꼈다.

📝정리되지 않은 하루 일과

데일리 스탠드업 미팅으로 오늘 할일에 대해 자각할 수 있었지만, 이에 대한 정리가 잘 이뤄지지 않았다. 기능의 어떤 부분에서 문제가 있었고, 그 문제를 해결했다면 어떻게 해결하였는지 체계적으로 정리하는 과정이 미흡했다. 다음 프로젝트에서는 기능 구현 중 문제가 생긴다면 팀원과 공유하고 기록해서 나의 개인블로그에도 추후에 정리하고 해결했다면 그것또한 기록하고 커밋에 남기고 팀원들과 공유하고 하는 일련의 프로세스를 잘 해내야겠다.

잘했던 점🙋🏻‍♂️

🗣중간발표 이후 개선된 팀원간 소통

2차 스프린트 미팅 이후 , 팀원들과 1주차 동안 아쉬웠던 점을 회고하였다. 특히, 팀원간의 소통이나 작업 진행 사항 공유등이 많이 부족했음을 느끼고 우리 팀 노션페이지를 만들어 각자 작업 진행 사항과 회의록, 공유하고 싶은 특이사항을 적어 교류하였다. 기능별로 페이지를 만들어 팀원들이 어떻게 코드를 구현했는지, 백엔드 개발자는 어떤 형식으로 데이터를 넘기는지 등 참고할 수 있어서 좋았다.

👏유익한 데일리 미팅

2차 스프린트 회고 이후, 미팅 준비에 있어서도 변화가 있었다. 트렐로와 노션 페이지에 각자 미팅 준비 자료를 간단하게 작성하였고, 그날 팀원들에게 보여줄만한 자료가 있다면 트렐로에 첨부하여 모든 팀원들이 시각적으로 보면서 회의를 진행하였다. 각자 따로 미팅에 대한 준비를 하고 진행한 스탠드업 미팅은 훨씬 압축적이면서도 유익했다.

profile
학습한 지식을 개인적으로 정리하기 위해 만든 블로그입니다 :)

0개의 댓글