[Magazine K Project] 상품 리스트 페이지

front-end developer·2022년 11월 3일
0

상품 리스트 페이지

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. 카테고리 메뉴 클릭 시 포커스온 기능

카테고리 포커스온(480p).mov

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>
  );
};

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

0개의 댓글