프론트엔드 개발자
류준열

메모이제이션이 필요한 순간

메모이제이션은 자주 쓰이는 값(or 함수)를 캐싱하여 리렌더링시 새로 만들지 않게 하는 것이다.
리액트에서는 useMemo, useCallback, memo 를 통해 구현할 수 있는데 여기서 굳이 설명하지는 않는다.

어쨌든 메모이제이션의 목적은 불필요한 리렌더링을 방지하는 것이다.

그래서 성능 최적화를 위해 자주 변하지 않는 부분은 메모이제이션을 하는 것인데, 때로는 성능최적화가 아니더라도 메모이제이션이 필요한 순간이 있다.

바로 리렌더링 되지 않아야 할 컴포넌트가 리렌더링 될 때 이다.

불필요한 리렌더링을 방지하는 가장 근본적인 방법

일단 가장 먼저 불필요한 리렌더링을 방지하는 가장 좋은 방법은 state 선언시 컴포넌트 트리 구조를 분석하여 적절한 컴포넌트에 선언하는 것이다.

즉, 불필요한 위치에 선언하지 않음으로 불필요한 리렌더링을 없애는 것이다.

그럼에도 불구하고 리렌더링을 막을 수 없거나, 구조상 state 선언이 리렌더링을 유발할 수 밖에 없다면 메모이제이션을 해야 하는데 나는 memo로 하는 편이다. (어떻게 하든 상관 없다.)

memo로 컴포넌트를 메모이제이션 하는 방법

React.memo 는 고차컴포넌트로 첫번째 인자에 컴포넌트를 받고, 두번째 인자에 ((prev,next))=>boolean 콜백함수를 받는다.

React.memo로 래핑된 컴포넌트는 이전 props와 다음 props가 같을 때 리렌더링 되지 않는다.

그런데 props가 객체일때는 항상 다른 참조를 가지기 때문에 prev.props === next.props 가 항상 false 이다.
(이해가 안가면 const a = {name : 'june'}; const b ={ name : 'june'}; a===b;을 콘솔에 쳐보자.)

이때 다음과 같이 memo의 두번째 인자를 이용할 수 있다. 두번째 인자에서 true를 반환하면 리렌더링 하지 않는다.

const Component = ({id,name}:PropsType) => {
    ...
}
// 이전 id와 현재 id 가 같으면 리렌더링 하지 않는다.
export default memo(Component,(prev,next)=>prev.id===next.id)

실제 상황

타이핑 할 때 마다 이미지도 리렌더링 되는 상황

아래 gif를 보면 input에 타이핑할때마다 input 의 value state를 참조하는 모든 컴포넌트가 리렌더링 되면서 이미지가 깜빡이는 상황이다.

메모이제이션 안되서 깜빡이는 현상

근본적인 해결책은 이미지가 input의 value state를 참조하지 않도록 하는 것이지만, 구조상 힘들다면 간단하게 img가 변하지 않을때는 컴포넌트를 memoization하여 해결할 수 있다.

// before
export default ProfileUpload

// after : 이전 props와 현재 props를 비교하여 img가 바뀌지 않았다면 
// ProfileUpload 컴포넌트를 메모이제이션 한다.
 export default React.memo(ProfileUpload, (prev, next) => {
   return prev.imgUrl === next.imgUrl;
});

img 변화 유무를 따져서 컴포넌트를 메모이제이션 한 모습이다. 메모이제이션하여 깜빡이 해결

타이머가 매 초 리렌더링 되면서 주변 컴포넌트도 리렌더링 되는 상황

회사에서 동료분이 타이머 깜빡이 해결이 안된다고 하셔서, 메모이제이션을 통해 해결한 사례이다. 메모이제이션 before

타이머가 업데이트 될 때마다 타이머 state를 참조하는 모든 컴포넌트가 리렌더링 되는 상황이다. 아래와 같이 props 변화 유무에 따라 컴포넌트를 메모이제이션 하여 해결할 수 있었다.

const SurveyBoardContent = ({ survey }: SurveyBoardContentProps) => {
  return (
    <SurveyBoardContentWrapper>
      <CozSurveyTheme>
        <Survey model={survey} />
      </CozSurveyTheme>
    </SurveyBoardContentWrapper>
  );
};

export default React.memo(SurveyBoardContent);

메모이제이션 after

메모이제이션은 꼭 필요한가?

아니다. 메모이제이션의 연산 비용이 리렌더링 비용보다 크다면 메모이제이션을 하는 것이 손해다.

나는 렌더링이 오래걸리거나(ex : 이미지) 변하지 않을 확률이 아주 높은데 여러번 리렌더링 되는 경우, 혹은 연산 비용이 큰 경우(여러 조건문)에 메모이제이션을 한다.

그리고 항상 같은 것을 렌더링하는데 리렌더링되는 경우에는 그냥 강제로 리렌더링을 막을 수도 있다.

memo(Component,()=>true)

어쨌든 메모이제이션이 꼭 필요한 건 아니니 개발자가 상황마다 판단하여 성능최적화를 하는 것이 좋다. (연산비용때문에 성능문제가 나타나기 전에는 안하는게 더 좋다는 글도 보았다.)