1차 프로젝트 종료 회고록

박희주·2022년 7월 7일
0

1차 프로젝트 회고

👉 프로젝트 기간: 22년 6월 20일 ~ 22년 7월 1일
👉 프로젝트 명: WENEIGE
👉 프로젝트 소스: Laneige 홈페이지
👉 프로젝트 구성원: 프론트엔드 4명, 백엔드 1명

  • 홈페이지의 최종 결과물(용량 문제상으로 빨리감기...)
    프로젝트 결과물

  • 프로젝트 결과 발표 사진
    프로젝트 발표

  • 프로젝트간 사용한 협업 툴

    • Trello
      Trello

    • Notion

1. 주요 구현 목표

1️⃣ 메인페이지 내 Carousel 기능 구현하기
2️⃣ 검색창 및 검색기능 구현하기

Carousel이 렌더링 되는 부분

<div
  className="inner"
  style={{ transform: `translateX(-${activeIndex * 100}%)` }}
>
  {carouselData.map(({ id, img, alt }) => {
    return (
      <div className="carouselItem" key={id}>
        <img alt={alt} src={img} />
      </div>
    );
  })}
</div>
  • 해당 부분은 Carousel이라고 하기 보단 이미지 슬라이드에 더 적합하다. 아무리 무한 루프로 돌아가는 Carousel을 구현해보려 했지만 검색했을 때는 라이브러리를 사용해서 구현하는 설명이 많았다.
    • 현재는 라이브러리 없이 자체적으로 구현하는 방법으로 라이브러리를 사용하지 않고 진행하였다.

Indicator버튼이 렌더링 되는 부분

{carouselData.map((el, index) => {
  return (
    <button
      key={index}
      className={`choose${index === activeIndex ? ' active' : ''}`}
      onClick={() => {
        updateIndex(index);
      }}
    />
  );
})}
  • Indicator버튼이 사진의 갯수(index)에 맞게 map을 통해서 생성이 되면서 index와 activeIndex라는 사진이 활성화 되는 index가 일치하면 active라는 className을 부여하면서 버튼에 활성화 중이라는 스타일을 주었다.

1-2. 메인페이지 검색 기능 구현하기

검색기능 구현하기

  • 최초에는 Nav에 검색창을 조건부 렌더링으로 searchIcon을 클릭했을 때 보여지게 되는걸로 세팅했으나 팀원들과 상의 하에 검색기능은 메인페이지에서만 활용하기로 결정하여 Nav에서 메인페이지 Carousel 하단으로 이동하였다.
/* 상품데이터를 담기위한 state 생성 */
const [productData, setProductData] = useState([]);

/* 상품데이터를 불러오기 위해 백엔드와의 소통 과정 */
useEffect(() => {
  fetch(API.PRODUCTS)
    .then(res => res.json())
    .then(data => {
      setProductData(data.product_detail);
    });
}, []);

/* 받아온 데이터를 기반으로 검색기능에 활용하기 위해 가공 */
const sortedItems = productData.filter(item => {
  return item.kor_name.includes(searchInputValue);
});

1-3. 메인페이지 정렬버튼 만들기

  • 메인페이지에 정렬되는 상품들을 "가나다 순"과 "가격 순(고가 순)"으로 정렬하게 기능을 하는 sortingButton이라는 컴포넌트를 제작하였다.
<button onClick={showList}>{buttonTitle}</button>
  {isShowOptions && (
    <SortButtonOptions
      showOptions={setIsShowOptions}
      titleChange={setButtonTitle}
      sortAscByLetter={sortAscByLetter}
      sortDescByPrice={sortDescByPrice}
    />
  )}
  • 정렬버튼을 클릭했을 때 option이 나올 수 있도록 SortingButtonOptions라는 컴포넌트를 만들고 버튼을 클릭했을 때 클릭한 버튼으로 버튼 제목이 변경될 수 있는 titleChange, 그리고 "가나다 순"과 "가격 순"으로 정렬될 수 있게 함수를 main페이지에 제작하고 함수들을 prop로 전달 하였다.
/* 가나다 순으로 정렬하는 함수 */
const sortAscByLetter = () => {
  let listSortedByKoreanAlphabet = [...productData].sort((a, b) =>
    a.kor_name > b.kor_name ? 1 : -1
  );
  setProductData(listSortedByKoreanAlphabet);
};

/* 가격 순으로 정렬하는 함수 */
const sortDescByPrice = () => {
  let listSortedByPrice = [...productData].sort((a, b) =>
    b.price > a.price ? 1 : -1
  );
  setProductData(listSortedByPrice);
};

2. 구현 간 Blocker

  • 구글링을 통해서 클론코딩으로 진행하며 코드를 이해해보고 우리 프로젝트에 맞게 수정하는 작업방식으로 진행하였다.
export const CarouselItem = ({ children, width }) => {
  return (
    <div className="carouselItem" style={{ width: width }}>
      {children}
    </div>
  );
};

const Carousel = ({ children }) => {
  const [activeIndex, setActiveIndex] = useState(0);
  
  return (
    <div className="carousel">
      <div
        className="inner"
        style={{ transform: `translateX(-${activeIndex * 100}%)` }}
      >
        {React.children.map(children, (child, index) => {
          return React.cloneElement(child, { width: "100%" });
        })}
      </div>
  • Children을 받아 map을 돌리는 방식으로 불필요한 컴포넌트가 발생하였다.
  • 또한 Children에 아직 미숙했고 cloneElement라는 방식을 처음 접하다 보니 매우 어렵게 느껴졌다.
  • 불필요한 컴포넌트가 있고 children을 토대로 cloneElement를 하는 방식이 매우 생소했다.

2-2. 검색 기능 구현

  • 검색기능 구현은 시작과 동시에 구상한 것은 아니였고 어느정도 레이아웃이 잡힌 뒤 기능에 들어갔었는데
    기능을 시작할 때 원래 홈페이지와 비교를 해보니 컴포넌트를 하나 더 만들어서 검색했을 때 Path Parameter를 적용해 /main/search라는 path를 가진 컴포넌트를 하나 더 만들어야 되는 상황이었다.
  • 마감 시간이 얼마 남지 않아 새로운 방색을 모색했는데 그 방법은 그냥 검색과 동시에 input value에 맞춰서 바로 해당되는 아이템을 렌더링 하는 방법이었다.
    • 원래는 해당 부분이 Nav에 있어야 했으나 기획을 변경해 Main페이지로 옮겼다.

2-3. 정렬 버튼 기능 구현

/* 최초 가나다 순 정렬 기능 구현 */
const abcSort = () => {
  let abcSorting = [...productData];
  let abcCompare = key => (a, b) => {
    return a[key] > b[key] ? 1 : a[key] < b[key] ? -1 : 0;
  };
  abcSorting.sort(abcCompare('title'));
  setProductData(abcSorting);
};

/* 최초 가격 순 정렬 기능 구현 */
const priceSort = () => {
  let priceSorting = [...productData];
  let priceCompare = key => (a, b) => {
    return a[key] < b[key] ? 1 : a[key] > b[key] ? -1 : 0;
  };
  priceSorting.sort(priceCompare('price'));
  setProductData(priceSorting);
};
  • 해당 기능을 작성할 때 위 처럼 데이터를 배열에 담아 배열에 접근해서 값을 비교를 하다보니 과정이 복잡해져서 코드가 길어지고 가독성이 떨어지는 문제가 발생했다.

3. Blocker 해결방안

  • 위의 Blocker들을 해결하기 위해 일단 Children에 대해 학습을 좀 더 해보았으나 명확하게 이해가 되지 않아 힘들었다.
  • 결국 Live Code Review때 멘토의 지도와 조언을 통해 Children으로 할 필요 없이 바로 데이터를 바탕으로 map을 하면 된다는 조언을 받았다.
    • CarouselData라는 state에 데이터를 담아서 이 데이터를 기반으로 map을 돌리기 시작했다.
      그렇게 나온 결과물은 다음과 같다.
    /* children으로 받지않고 데이터를 직접받아 map을 돌려 기존에 CarouselItem이라는
    불필요한 컴포넌트를 삭제했다. */
    {carouselData.map(({ id, img, alt }) => {
      return (
        <div className="carouselItem" key={id}>
          <img alt={alt} src={img} />
        </div>
      );
    })}
  • 확실히 children을 하는 방식에서는 코드의 시인성이 떨어져 보였는데 데이터를 활용해 바로 map을 돌리니 간결하고 보기 훨씬 편해졌다.
  • indicator버튼도 children을 받아서 렌더링 되는 형식이였으나 마찬가지로 데이터를 직접 활용해서 구현을 하였다.

3-2. 검색 기능 구현

  • 검색 기능 구현간 Blocker는 상위 부분에 제작한 부분으로 Blocker라고 하기엔 애매한 부분이나 마주친 곤경에 해당하긴 한다.
  • 기존에 받아오는 데이터를 그냥 단순히 list로 보여주는 역할만 했으나 그 데이터를 filter메소드를 활용해서 input value에 맞춰 정렬된 데이터를 렌더링 하는 형식으로 변동하였다.
const sortedItems = productData.filter(item => {
  return item.kor_name.includes(searchInputValue);
});
  • 위의 sortedItems라는 기존 데이터로 map을 돌리던 컴포넌트로 props로 전달하고 해당 데이터를 map을 돌려 렌더링 되게끔 세팅 하였다.

3-3 정렬 버튼 기능 구현

  • 최초에 작성한 함수가 가독성이 떨어지고 불필요하게 배열로 접근할 필요가 없었는데 Live Code Review때 멘토의 조언으로 이렇게 복잡하게 할 필요 없이 간단하게 하는 방법이 있다는 설명을 들었다.

    • 배열에 담지 말고 바로 객체의 key값을 뽑아서 비교를 하라는 방법이었다.
    /* 가나다 순으로 정렬하는 함수 */
    const sortAscByLetter = () => {
      let listSortedByKoreanAlphabet = [...productData].sort((a, b) =>
        a.kor_name > b.kor_name ? 1 : -1
      );
      setProductData(listSortedByKoreanAlphabet);
    };
    
    /* 가격 순으로 정렬하는 함수 */
    const sortDescByPrice = () => {
      let listSortedByPrice = [...productData].sort((a, b) =>
        b.price > a.price ? 1 : -1
      );
      setProductData(listSortedByPrice);
    };
    • 배열안에 배열이 아닌 직접 객체를 배열안에 sort를 시켜 해당 key에 객체로 직접 접근하는 방식으로 비교하고자 하는 key값에 접근해 value를 뽑아 낼 수 있었다.

4. 느낀점

4-1. 팀원들과의 의사소통 문제(특히 백엔드와...)

  • 처음에 mock-data를 학습했을 때 데이터가 무조건 mock-data형식으로 들어오게 될 줄 알고 fetch를 해서 데이터를 받아오는 것을 신경안썼었다.

    • 하지만 실전으로 백엔드와 소통을 해보았을 때 서로가 정해놓은 key값도 모두 달랐고 데이터가 들어오는 형식이 데이터가 하나의 큰 객체에 담겨서 오는것을 확인하고 fetch요청하는 코드들을 전반적으로 다시 수정했었다.
    // 원래는 이러한 형태로 데이터를 만들어 연습했으나...
     [
      {
        "id": 1,
        "img": "/images/main/시그니처 브로우 펜슬.jpeg",
        "title": "시그니처 브로우 펜슬",
        "price": "39000원",
        "hashtxt1": "#Meets Arts",
        "hashtxt2": "#Artist NOVO"
      },
      {
        "id": 2,
        "img": "/images/main/리퀴드 아이라이너.jpeg",
        "title": "리퀴드 아이라이너",
        "price": "28000원",
        "hashtxt1": "#나만의 네오",
        "hashtxt2": "#톤떡궁합"
      },
     ]
    
    // 실제로 오는 데이터는 이러한 형식이었다.
    product_detail: {
     [
      {
        "id": 1,
        "img": "/images/main/시그니처 브로우 펜슬.jpeg",
        "title": "시그니처 브로우 펜슬",
        "price": "39000원",
        "hashtxt1": "#Meets Arts",
        "hashtxt2": "#Artist NOVO"
      },
      {
        "id": 2,
        "img": "/images/main/리퀴드 아이라이너.jpeg",
        "title": "리퀴드 아이라이너",
        "price": "28000원",
        "hashtxt1": "#나만의 네오",
        "hashtxt2": "#톤떡궁합"
      },
     ]
    }

4-2. 협업 툴 사용에 대한 미숙...

  • 항상 수기로 공책에 적거나 아이패드를 활용해 필기에 익숙해져 있었다보니 Trello라는 툴을 처음 사용해봤는데 익숙치가 않아 잘 건드리지 않았었다.
  • 수기로 적는 습관에서 팀끼리 맞춰서 사용하는 협업툴을 습관들여야 겠다는 다짐을 했다.

4-3. 성장과 성취감

  • 기능들을 구현하면서 특히 React를 활용해 이런저런 기능들을 많이 구현해봤는데 매우 재미있었다.
    특히 각자 git으로 branch를 만들어서 작업을 한다음 merge가 되어 master branch에서 서로 다른 공간에서 만든 작업물들이 합쳐져서 유기적으로 작동하는 모습을 처음 마주하니까 엄청난 성취감과 기쁨이 몰려왔다.
    • 위의 과정을 하도 하다보니 git이 처음에는 무서웠었는데 이제는 git에도 익숙해져서 명령어가 낯설지 않아졌다.
  • 처음 프로젝트를 해보게 되었는데 물론 순탄하게 작업이 되지 않고 막히는 부분도 많았지만 여러사람들이 모여 각자 자기만 하는 작업이 아니고 팀원들과의 peer review를 통해서도 해결을 한 부분이 많았고 구글링으로도 해결하는 부분이 많았다.
    • 막히던 부분에서 내가 구글링으로 스스로 해결방법을 찾아내고 또한 해결해 냈을 때 성취감은 이루 말할 수 없었다.
    • 나도 모르는 부분이 많은 상황이지만 다른 사람들에게 다양한 방법으로 설명하고있는 내 모습을 보니 그래도 과거 아예 말도 못하던 때에서 많이 성장을 했다는 점을 느꼈고 지금까지 해왔던 공부가 헛되게 하지 않았다라는 것을 확실하게 느꼈다.

5. 결론

1️⃣ 사람들이 있는 공간에서는 소통은 필수다!
2️⃣ 안됀다고 말로만 하지 말고 스스로 해결방법을 찾아내자!
3️⃣ 모든 답은 가까이 있다!
4️⃣ 내가 설명을 한다해서 많이 안다는 것이 아니다! 뭐든지 항상 궁금해 해야한다!

profile
하나부터 열까지, 머리부터 발 끝까지

0개의 댓글