1차 프로젝트 후기 : 개발 관점에서

JHyeon·2020년 12월 27일
1

되돌아보기

목록 보기
4/5
post-thumbnail

마켓컬리 후기는 따로 작성하였다. 이 글에서는 내가 기술적으로 어떤 경험을 했고 어떤 성장을 했는지 기록한다.

프로젝트 소개

프로젝트 시연 영상 링크

프로젝트 기간

  • 2020.12.14 ~ 2020.12.24 (총 11일)

Front-End

  • HTML, CSS, JavaScript
  • React(CRA)
  • Sass

Back-End

  • Django
  • CORS Header
  • Bcrypt
  • PyJWT
  • MySQL
  • AqueryToo

협업 도구

  • Git, Github
  • Slack : 비대면 소통
  • Trello : 일정관리 및 작업 현황 공유
  • Notion : 팀 내 개발 자료, 규칙, 안건 등 기록

내가 맡은 역할

제품 상세 페이지, 장바구니, 결제 페이지, Aside, Footer

제품 상세 페이지의 데이터는 백엔드로부터 받아온다. 같은 페이지 내에서 매뉴탭에 따른 스크롤 이동이 있으며 상품 종류별 동적 라우팅이 가능하다. 상품 리뷰, 상품 문의 게시판 페이지네이션도 상세 페이지에 있다.

장바구니 페이지는 백엔드로부터 받아온 장바구니 데이터를 냉동, 냉장, 상온 상품별로 필터링하여 섹션 구분 및 섹션 접기 펴기가 가능하다. 장바구니에서 사용자의 각 액션(수량 변경, 삭제, 선택, 전체 선택, 선택 삭제 등)마다 백엔드와 통신을 한다. 실시간으로 무료 배승 가능 유무, 총 마일리지 적립액, 총 할인액, 총 결제액 등을 계산한다.

내가 배운 것

1. 리액트 활용

  • 라이프 사이클 활용
  • 재사용 가능한 컴포넌트 작성

페이지네이션을 구현할 때와 비동기 함수를 사용할 때 라이프 사이클을 이해하고 사용하였다. 프로젝트 중 모든 컴포넌트를 처음으로 합쳤을 때 서로의 페이지에 라우팅을 하는 기능이 당연히 있었다. 제품 상세 페이지로 이동할 때, 브라우저에서 현재 스크롤 위치에 고정된 채로 이동하는 문제가 발생했다. 제품 상세 페이지로 이동할 때는 스크롤이 가장 위로 올라가길 바랬는데! 그런데 그 현상을 보고 'componentDidUpdate' 에 그냥 윈도우 scroll값을 0으로 주면 되겠는데? 라는 생각이 바로 들었고, 구글링 하지 않고 바로 해결했다는 것이 뿌듯했다.

게시판 컴포넌트가 중복되었지만, 게시판 형태는 조금 달랐다. 조건부 렌더링과 게시판 컴포넌트가 props로 받는 값을 통해 다른 게시판이 생기더라도 유동적으로 대응할 수 있는 재사용 가능한 컴포넌트를 작성하였다.

2. 비동기 코드 작성하기

  • 백엔드와 통신에서 async await 문법 활용
  • 비동기 관련 문제 해결

처음에는 fetch().then().then() 의 형태로 코드를 짜려고 했다. 하지만 비동기 문법을 학습하고 활용해보지 않았기에 시도해 보았다. 특히 장바구니 페이지에서는 하나의 사용자 액션에 2번의 백엔드 통신이 이루어졌기에 비동기 순서가 중요한 과정이 있었다. 그 과정에서 백엔드와 통신 종류별로 async 함수를 따로 작성하여 비동기 문제를 깔끔하게 해결할 수 있었다. 장바구니 아이템에 대해 삭제 요청(DELETE), 수량 변경/선택 토글 요청(PATCH)이 끝난 후에 다시 GET method로 변경된 장바구니 데이터를 가져오는 형태였다. 두 요청을 async 함수로 분리하여 작성했다.

// 비동기 순서를 직관적으로 만들어주는 async await 문법.
selectItem = async e => {
  const id = e.target.id;
  const className = e.target.className;

  const response = await fetch(`${CART_API}`, {
    method: "PATCH",
    headers: {
      Authorization: localStorage.getItem("token"),
    },
    body: JSON.stringify({
      cart_item_id: id,
      select: className === "fa-check-circle fas purple" ? "False" : "True",
    }),
  });
  // error handle codes
  
  // 그 다음 실행시킬 async 함수이다.
  // 동기적인 형태로 위에서 아래로 코드를 읽을 수 있다는 장점이 있다.
  this.getCartData();
};

서버가 항상 켜져있는 상황이 아니었기에 aysnc 문법을 활용할 때 아래와 같이 try catch 를 활용하여 서버가 꺼져있을 때 목데이터를 가져오는 코드를 사용할 수 있었다. fetchWithTimeout 은 사용자 함수로 최대 fetch 시간을 내가 정해서 일정 시간이 지나면 강제로 목 데이터를 화면에 띄우도록 설정하였다.

getCartData = async () => {
  try {
    const response = await fetchWithTimeout(`${CART_API}`, {
      method: "GET",
      headers: {
        Authorization: localStorage.getItem("token"),
      },
    });
    const data = await response.json();
    this.setState({ cartData: data.items_in_cart });
  } catch {
    const response = await fetch(`data/cartdata.json`);
    const data = await response.json();
    let cartData = data.items_in_cart;
    cartData = cartData.map(data => {
      data.selected = true;
      return data;
    });

    this.setState({ cartData: cartData });
  }
};

만약 fetch().then().then() 이었다면 then 안에 다시 비동기 코드가 들어가는 형태의 코드가 되었을 것이다.

3. 깔끔한 코드 작성하기

  • 가독성이 좋으며 수정 가능한 코드 작성하기
  • Short-circuit, 삼항 연산자, 고정 값 변수로 분리하기 등 활용

아래는 구현한 페이지네이션과 코드에서 고정값으로 분리했던 변수들이다.

const LIMIT_PER_PAGE = 10; // 한 페이지 최대 게시글 수
const PAGES_NUM = 5; // 한 페이지 내 페이지네이션 갯수 5개
const INITIAL_PAGES = Array.from({ length: PAGES_NUM }, (_, i) => i + 1); // 초기 페이지 1~5

//pages의 초기 값은 INITIAL_PAGES이다.
{pages.map((page, idx) => {
  return (
    <li key={idx}>
      <button
      id={pages[idx]}
      className={currentPage === pages[idx] ? "current-page" : ""}
      onClick={clickPage}
      >
      {page}
    </button>
    </li>
  );
})}

위 코드에서 고정값인 변수들만 바꾸어주면 바로 원하는 형태가 나온다. 필요에 따라(실제로 이메일 사이트에 이런 기능이 흔히 있다) 사용자에게 위 값을 변경하도록 코드를 수정할 수도 있다.

아래는 자주 활용했던 형태의 코드로 short-circuit을 활용하여 조건부 렌더링을 구현하였다. 필요에 따라 정보를 숨기거나 보여줄 수 있다.

{showHits && <th className="lookup">조회</th>}

리액트에서 사용자 컴포넌트를 차례대로 한 화면에 보여주기 위해 아래와 같은 코드를 작성하였다.

const MENU_COMPONENTS = {
  1: ItemDescription,
  2: ItemImage,
  3: DetailInfo,
  4: CustomerReview,
  5: ItemInquire,
};
const MENU_NAME = ["ItemDescription", "ItemImage", "DetailInfo", "CustomerReview", "ItemInquire"];

{MENU_NAME.map((name, idx) => {
  const ComponentName = MENU_COMPONENTS[idx + 1];
  return (
    <div name={name} key={name}>
      <ItemDetailMenu menuTabId={idx + 1} scrollToMenu={this.scrollToMenu} />
      <ComponentName
        paramsid={this.props.match.params.id}
        menuTabId={idx + 1}
        itemData={itemData}
        />
    </div>
  );
})}

위의 다섯가지 메뉴 컴포넌트는 가장 위에 아래와 같은 메뉴탭이 공통적으로 존재하며 메뉴 탭을 누르면 해당하는 위치로 스크롤하게 된다.

4. 타인의 코드 읽는 능력 향상

프로젝트 전에도 그렇고 프로젝트 중에도 나는 질문을 많이 받는 편이었다. 내가 읽은 절대적인 코드량을 비교하면 내 코드보다 다른 사람의 코드를 훨씬 많이 읽었다. 그리고 확실히 읽는 속도가 빨라졌다. 피아노도 초견을 계속하다보면 늘 수 밖에 없는 것과 비슷하다고 생각했다. 그리고 배운 점들이 많다. 솔직히 초보 개발자끼리 작성하는 코드가 막 뛰어나거나 하진 않을 것이다. 하지만 반대로 어떤 코드가 가독성이 낮은지 알 수 있었고, 그런 코드 작성을 피해야 한다는 것을 알기도 했다.

글을 잘 쓰는 사람의 글은 술술 읽힌다. 코드도 마찬가지였다. 코드를 잘 작성하는 사람은 슥 읽어도 그 코드가 어떤 코드인지 알 수 있었다. 이는 협업과 유지보수에서 엄청난 장점이 될 것이라고 생각했다. 코드를 잘 작성한 분에게 질문을 받을 때는 질문의 요지와 발생한 문제를 빠르고 정확하게 이해할 수 있었다. 그리고 잘 쓴 코드일 수록 해결 방법도 깔끔하게 제시할 수 있었다. 반대로 말하면 스파게티 코드는 문제가 발생해도 그 해결책을 제시받기 어렵다는 뜻이기도 하다.

스택오버플로우 등에 질문을 할 때 질문도 잘 해야한다고 한다. 본인의 코드가 잘 작성이 되었다면 다른분들에게 설명하기도 쉬울 것이다. 즉, 좋은 코드에서 문제가 발생했다면 스스로 해결하기도 훨씬 용이해 질 것이고 도와주는 사람의 입장에서도 어렵지 않게 좋은 해결책을 제시할 가능성이 높다는 뜻이다.

많은 것을 배울 수 있었던 프로젝트였다.

profile
The only thing that interferes with my learning is my education.

0개의 댓글