테오의 스프린트 4일차 - 개발 진행 & 의견 공유하기

Sheryl Yun·2022년 6월 21일
1

테오의 스프린트

목록 보기
4/5

4일차

드디어 개발 단계가 시작되었다. 그동안 구체화한 아이디어들과 합의한 기술 스택들로 마지막날 발표할 때까지 서비스를 구현하여 배포하는 과정이다.

기획 단계에서 나누었던 아이디어가 상당히 많아서 '다 구현을 할 수 있을까' 다들 걱정을 했지만 워낙 그만큼 화면과 기능이 구체화되어 있어서 그런지 생각보다 사이트가 슥슥 만들어졌다. 그런데 사이트를 개발하던 도중 홈 화면을 같이 개발하던 팀원과 의견 차이가 생겼다.

🤔 JSX 엘리먼트를 어디에 두어야 할까..?

필터 기능인 함수 renderCardData를 구현할 때였는데, 팀원은 함수 내에서 JSX 엘리먼트까지 반환하고 JSX 내부에서 함수를 호출하는 코드를 작성했다. 하지만 나는 JS를 다루는 건 함수에 일임하고 JSX는 렌더링 역할만 담당해야 한다고 생각해서, 다 만들어진 filteredData를 map으로 꺼내는 부분부터는 JSX에 포함시키자고 제안했다.

팀원이 생각한 방식

   const renderCardData = () => {
    const filteredData = cardData
      .filter((data) => {
        if (!cardFilter.skill) return true;
        return data.tags.includes(cardFilter.skill);
      })
      .filter((data) => {
        if (!cardFilter.category || cardFilter.category === '전체') return true;
        return data.category === cardFilter.category;
      });

	// 함수 내부에서 JSX 엘리먼트 반환
    return filteredData.map((card: PostType, idx) => (
      <Link key={idx} to={'/detail/' + card.id}>
        <Card card={card} />
      </Link>
    ));
  };
  
 return (
    <Layout>
      (...)
      <CardContainer>{renderCardData()}</CardContainer>
    </Layout>
  );

내가 생각한 방식

   const renderCardData = () => {
    const filteredData = cardData
      .filter((data) => {
        if (!cardFilter.skill) return true;
        return data.tags.includes(cardFilter.skill);
      })
      .filter((data) => {
        if (!cardFilter.category || cardFilter.category === '전체') return true;
        return data.category === cardFilter.category;
      });

	// 함수 내부에서는 조작이 끝난 데이터 반환 (useState 등에 담음)
    return filteredData;
   }

	return (
       <Layout>
           <CardContainer>{filteredData.map((card: PostType, idx) => 
              <Link key={idx} to={'/detail/' + card.id}>
                 <Card card={card} />
              </Link>
           )}</CardContainer>
       </Layout>
	);

팀원의 근거

  1. JSX 안에서 코드가 길어지는 것이 가독성 차원에서 좋아 보이지 않는다.
  2. 데이터가 변경될 때마다 전체 JSX가 리렌더링될 때 JSX 내부의 renderCardData 함수도 매번 호출된다. 성능 상으로 약간의 차이는 있겠지만 기본적으로 기능은 잘 동작할 것이다.
  3. 최종 조작된 배열 데이터에서 꺼내는 li 컴포넌트에 key가 부여되는데, 이 key가 변경된 컴포넌트와 그렇지 않은 컴포넌트와 구분해줄 것이다.

나의 근거

  1. 리액트에서 JSX는 렌더링을 하는 영역이고, return문 위쪽은 데이터를 조작하는 자바스크립트의 영역이다. 리액트가 View 라이브러리를 표방하며 JSX 개념을 도입한 만큼, 리액트에서 추구하는 방향대로 데이터는 JS 영역에서 조작하고 렌더링은 JSX에게 맡기는 게 좋을 것 같다.
  2. key가 구분해주는 것은 map으로 렌더링되는 li 컴포넌트 자체이고, JSX에서의 함수 호출 여부와는 크게 상관이 없다고 생각된다.

팀원이 2번째 근거로 말한 리렌더링 내용은 사실 확실히 이해가 된 부분은 아니었는데, 다른 팀원 한 분이 스프린트의 단체 카톡방에서 이 문제에 대해 다음과 같이 얘기해주었다.

리액트는 가상 DOM을 그려서 브라우저 메모리에 올려놓고, diff 알고리즘을 통해 변경이 있는 부분만 바꾸는 방식으로 리렌더링을 한다.
그런데 return문에 함수를 넣어두면 가상 DOM의 DOM 오브젝트로 등록되지 않고 함수로 등록되기 때문에, DOM에 변경사항이 없어도 매번 리렌더링되는 성능적인 이슈가 있을 것 같다.

답변을 보고 나니 원티드 프리온보딩 코스의 수업에서 들었던 이야기가 생각났다. '리액트에서 거의 유일하게 신경써야 하는 성능 최적화 관련 이슈는 리렌더링의 횟수이다'

하지만 여차저차한 끝에 결과적으로는 팀원의 의견대로 진행되었다. (우선은 코드가 제대로 작동을 했고 빡빡한 프로젝트 일정을 생각했을 때 길게 끌 만한 논의는 아니라고 생각)
리액트의 리렌더링 횟수나 단일 책임 원칙 등에 대해 다시 한번 생각해볼 수 있는 계기였다.

추가: 프로젝트 이후

프로젝트 이후에 개인적으로 리액트의 li key에 대해 좀 더 논의하고 싶어서 예전에 정리했던 링크를 단톡방에 공유했다. (map의 index를 key로 사용하면 안되는 이유) 홈 화면을 같이 개발했던 팀원이 이 자료를 보더니, 내가 코드 중에 ${idx}_{content}라고 작성한 부분이 개인적인 취향인 줄 알았다고 했다. (저도 당근 면접 아니었으면 몰랐을 내용이에요 ㅠㅠ)

또 map 렌더링 부분을 JSX로 빼야 한다는 의견을 좀 더 보충하기 위해서 map이 여러 가지 일을 할 수 있지만, 이번 프로젝트에서 사용된 map은 문맥상 '데이터 렌더링' 자체에 좀 더 초점을 맞추고 있으니 리액트에서 렌더링의 역할을 담당하는 JSX에 쓰는 게 맞을 것 같다고 한 번 더 조심스럽게 얘기를 했다.

개발할 때 동작만 잘 되는 코드는 별 의미 없다고 생각한다. (나중에 미래의 나와 다른 동료와 무한히 계속 될 수 있는 유지보수 등을 생각하면) 그래서 협업할 때 이러한 부분이 공감대가 이루어지면 좋겠다고 생각한다.

profile
데이터 분석가 준비 중입니다 (티스토리에 기록: https://cherylog.tistory.com/)

0개의 댓글