React를 사용하면서 최적화를 하기 위해 많은 고민을 한다. 그 중 React.memo()도 사용하게 되는데 이러한 React.memo()는 언제 사용하는 것이 가장 효과적인지 알아보자.
UI 성능을 증가시키기 위해, React는 고차 컴포넌트(Higher Order Component, HOC)React.memo()를 제공한다. 렌더링 결과를 메모이징(Memoizing)함으로써, 불필요한 리렌더링을 건너뛴다.
컴포넌트가 React.memo()로 래핑 될 때, React는 컴퍼넌트를 렌더링하고 결과를 메모이징한다. 그리고 다음 렌더링이 일어날 때 props가 같다면, React는 메모이징된 내용을 재사용한다.
여기서 가장 중요한 것이 바로 방금 나왔던 다음 렌더링이 일어날 때 props가 같다면, React는 메모이징된 내용을 재사용한다 라는 부분이다.
즉 메모이징 한 결과를 재사용 함으로써, React에서 리렌더링을 할 때 가상 DOM에서 달라진 부분을 확인하지 않아 성능상의 이점을 누릴 수 있다.
위의 강조했던 문장을 계속 생각하면 언제 사용해야 하는지 쉽게 알아차릴 수 있다. 같은 props로 렌더링이 자주 일어나는 컴포넌트에 사용하는 것이다.
아래의 예시를 살펴보자 (TOAST UI에서 가져온 예시이다.)
Movie의 부모 컴포넌트인 실시간으로 업데이트되는 영화 조회수를 나타내는 MovieViewsRealtime 컴포넌트가 있다.
function MovieViewsRealtime({ title, releaseDate, views }) {
return (
<div>
<Movie title={title} releaseDate={releaseDate} />
Movie views: {views}
</div>
);
}
이 어플리케이션은 주기적으로 서버에서 데이터를 폴링(Polling)해서 MovieViewsRealtime 컴포넌트의 views를 업데이트한다.
// Initial render
<MovieViewsRealtime
views={0}
title="Forrest Gump"
releaseDate="June 23, 1994"
/>
// After 1 second, views is 10
<MovieViewsRealtime
views={10}
title="Forrest Gump"
releaseDate="June 23, 1994"
/>
// After 2 seconds, views is 25
<MovieViewsRealtime
views={25}
title="Forrest Gump"
releaseDate="June 23, 1994"
/>
// etc
views가 새로운 숫자가 업데이트 될 때 마다 MovieViewsRealtime 컴포넌트 또한 리렌더링 된다. 이때 Movie 컴포넌트 또한 title이나 releaseData가 같음에도 불구하고 리렌더링 된다.
이러한 상황일때 우리는 최적화를 위해 React.memo()를 사용한다.
React.memo()를 사용하여 title 혹은 releaseDate props가 같다면, React는 MemoizedMovie를 리렌더링 하지 않을 것이다. 이렇게 MovieViewsRealtime 컴퍼넌트의 성능을 향상할 수 있다.
우리는 함수의 동등성이란 함정 때문에, 메모이제이션을 적용할 때는 콜백을 받는 컴포넌트 관리에 주의해야한다. 리렌더를 할 때 마다 부모 함수가 다른 콜백 함수의 인스턴스를 넘길 가능성이 있다.
즉 동일한 props 값을 전달하더라도 새로운 콜백 때문에 리렌더링을 하게 되는 경우가 발생하는 것이다. 이러한 문제를 해결하기 위해 새로운 콜백이 발생하지 않게 useCallback()을 사용해서 콜백 인스턴스를 보존시켜준다.
이러한 이유로 React.memo() 함수를 사용하는 것이 효과적이라고 판단할 때 콜백 함수에도 신경을 써야 한다.
React.memo()를 찾아보면 useMemo도 있다는 것을 알게 된다. 그러면 useMemo는 React.memo()와 같을까?
정답은 아니다. 종류가 다르다.
useMemo는 메모이즈된 값을 return하는 hook이다. 인자로 함수와 의존 값(dependencies)을 받는다. useMemo는 두번째 인자로 준 의존 인자 중에 하나라도 변경되면 값을 재 계산한다. 이를 통해 매 렌더시마다 소요되는 불필요한 계산을 피할 수 있다. 만약 dependencies 인자로 아무것도 전달되지 않는다면, 렌더시마다 항상 값을 새롭게 계산하여 return한다.
이러한 useMemo는 hook이고 React.memo()는 HOC으로 다른 종류이다.
하지만 공통점도 존재한다. React.memo와 useMemo 모두 이전 props와 동일하면 인자로 넘긴 함수는 재실행되지 않고, 이전의 메모이즈된 결과를 반환한다는 점에서 동일하게 동작한다.
즉 우리가 알아보려고 했던 React.memo()를 사용할 상황은 어떤 상황인지 알게 되었다. 그러므로 아무런 상황에나 React.memo()를 남발하게 된다면 렌더링을 효율적이게 하려는 의도에 반대의 효과를 얻을 수 있으므로 정확한 상황에만 사용하자
참고
https://ui.toast.com/weekly-pick/ko_20190731
https://sustainable-dev.tistory.com/137