드디어 개발 단계가 시작되었다. 그동안 구체화한 아이디어들과 합의한 기술 스택들로 마지막날 발표할 때까지 서비스를 구현하여 배포하는 과정이다.
기획 단계에서 나누었던 아이디어가 상당히 많아서 '다 구현을 할 수 있을까' 다들 걱정을 했지만 워낙 그만큼 화면과 기능이 구체화되어 있어서 그런지 생각보다 사이트가 슥슥 만들어졌다. 그런데 사이트를 개발하던 도중 홈 화면을 같이 개발하던 팀원과 의견 차이가 생겼다.
필터 기능인 함수 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>
);
팀원이 2번째 근거로 말한 리렌더링 내용은 사실 확실히 이해가 된 부분은 아니었는데, 다른 팀원 한 분이 스프린트의 단체 카톡방에서 이 문제에 대해 다음과 같이 얘기해주었다.
리액트는 가상 DOM을 그려서 브라우저 메모리에 올려놓고, diff 알고리즘을 통해 변경이 있는 부분만 바꾸는 방식으로 리렌더링을 한다.
그런데 return문에 함수를 넣어두면 가상 DOM의 DOM 오브젝트로 등록되지 않고 함수로 등록되기 때문에, DOM에 변경사항이 없어도 매번 리렌더링되는 성능적인 이슈가 있을 것 같다.
답변을 보고 나니 원티드 프리온보딩 코스의 수업에서 들었던 이야기가 생각났다. '리액트에서 거의 유일하게 신경써야 하는 성능 최적화 관련 이슈는 리렌더링의 횟수이다'
하지만 여차저차한 끝에 결과적으로는 팀원의 의견대로 진행되었다. (우선은 코드가 제대로 작동을 했고 빡빡한 프로젝트 일정을 생각했을 때 길게 끌 만한 논의는 아니라고 생각)
리액트의 리렌더링 횟수나 단일 책임 원칙 등에 대해 다시 한번 생각해볼 수 있는 계기였다.
프로젝트 이후에 개인적으로 리액트의 li key에 대해 좀 더 논의하고 싶어서 예전에 정리했던 링크를 단톡방에 공유했다. (map의 index를 key로 사용하면 안되는 이유) 홈 화면을 같이 개발했던 팀원이 이 자료를 보더니, 내가 코드 중에 ${idx}_{content}
라고 작성한 부분이 개인적인 취향인 줄 알았다고 했다. (저도 당근 면접 아니었으면 몰랐을 내용이에요 ㅠㅠ)
또 map 렌더링 부분을 JSX로 빼야 한다는 의견을 좀 더 보충하기 위해서 map이 여러 가지 일을 할 수 있지만, 이번 프로젝트에서 사용된 map은 문맥상 '데이터 렌더링' 자체에 좀 더 초점을 맞추고 있으니 리액트에서 렌더링의 역할을 담당하는 JSX에 쓰는 게 맞을 것 같다고 한 번 더 조심스럽게 얘기를 했다.
개발할 때 동작만 잘 되는 코드는 별 의미 없다고 생각한다. (나중에 미래의 나와 다른 동료와 무한히 계속 될 수 있는 유지보수 등을 생각하면) 그래서 협업할 때 이러한 부분이 공감대가 이루어지면 좋겠다고 생각한다.