Published on

React.memo 사용하기

React.memo와 렌더링 성능

UI 성능을 증가시키기 위해, React는 고차 컴포넌트(HOC) React.memo를 제공한다.
렌더링 결과를 메모이징(Memoizing)함으로써, 불필요한 리렌더링을 건너뛴다.

React는 먼저 컴포넌트를 렌더링(rendering) 한 뒤, 이전 렌더된 결과와 비교하여 DOM 업데이트를 결정한다.
만약 렌더링 결과가 이전과 다르다면, React는 DOM을 업데이트한다.
다음 렌더링 결과와 이전 결과의 비교는 빠르다. 하지만 어떤 상황에서는 이 과정의 속도를 좀 더 높일 수 있다.

컴포넌트가 React.memo()로 래핑 될 때, React는 컴넌트를 렌더링하고 결과를 메모이징(Memoizing)한다.
그리고 다음 렌더링이 일어날 때 props가 같다면, React는 메모이징(Memoizing)된 내용을 재사용한다.

export function Movie({ title, releaseDate }) {
  return (
    <div>
      <div>Movie title: {title}</div>
      <div>Release date: {releaseDate}</div>
    </div>
  );
}

export const MemoizedMovie = React.memo(Movie);

메모이징 한 결과를 재사용 함으로써, React에서 리렌더링을 할 때 가상 DOM에서 달라진 부분을 확인하지 않아 성능상의 이점을 누릴 수 있다.


props 비교 커스터마이징

React.memo()는 props 혹은 props의 객체를 비교할 때 얕은(shallow) 비교를 한다.
비교 방식을 수정하고 싶다면 React.memo() 두 번째 매개변수로 비교함수를 만들어 넘겨주면 된다.

const Checks = ({ checkList, labels, onCheck }) => {
  return (
    <ul>
      {labels.map((label, idx) => (
        <li key={idx}>
          <label>
            <input
              type="checkbox"
              checked={checkList[idx]}
              onChange={() => {}}
              onClick={() => onCheck(idx)}
            />
          </label>
        </li>
      ))}
    </ul>
  );
};

export default React.memo(Checks, (prevProp, nextProp) => {
  let i,
    len = nextProp.checkList.length;

  for (i = 0; i < len; i += 1) {
    if (prevProp.checkList[i] !== nextProp.checkList[i]) {
      return false;
    }
  }
  return true;
});

비교 함수가 false를 리턴하면 props가 변경된 것으로 보고 리렌더링을 한다.


React.memo는 언제 사용해야 하나

React.memo()를 사용하기 좋은 케이스는 함수형 컴포넌트가 같은 props로 자주 렌더링이 발생할 것으로 예상 될 때이다.
일반적으로 부모 컴포넌트에 의해 하위 컴포넌트가 같은 props로 리렌더링 될 때가 있다.
이 때 React.memo()를 사용하면 성능을 개선할 수 있다.

위와 같은 상황이 아니라면 React.memo를 사용하지 않는다.
불필요하게 React.memo를 사용한다면 메모이제이션으로 인한 메모리점유, 불필요한 props비교 로직수행으로 성능이 오히려 안 좋아질 수 있다.


React.memo와 콜백함수

부모 컴포넌트가 자식 컴포넌트의 콜백 함수를 정의한다면, 리렌더링을 할 때 마다 부모함수가 다른 콜백 함수의 인스턴스를 넘길 수 있다.
그러면 자식 컴포넌트가 전달받는 다른 props들에 변화가 없더라도 콜백 함수의 인스턴스가 다르기 때문에 리렌더링이 발생할 수 있다.

function Logout({ username, onLogout }) {
  return <div onClick={onLogout}>Logout {username}</div>;
}

const MemoizedLogout = React.memo(Logout);

function MyApp({ store, cookies }) {
  return (
    <div className="main">
      <header>
        <MemoizedLogout username={store.username} onLogout={() => cookies.clear()} />
      </header>
      {store.content}
    </div>
  );
}

위 문제를 해결하려면 onLogout prop의 값을 매번 동일한 콜백 인스턴스로 설정해야만 한다.
useCallback()을 이용해서 콜백 인스턴스를 보존시킨다.

const MemoizedLogout = React.memo(Logout);

function MyApp({ store, cookies }) {
  const onLogout = useCallback(() => {
    cookies.clear();
  }, []);
  return (
    <div className="main">
      <header>
        <MemoizedLogout username={store.username} onLogout={onLogout} />
      </header>
      {store.content}
    </div>
  );
}

useCallback(() => { cookies.clear() }, []) 는 항상 같은 함수 인스턴스를 반환한다.
MemoizedLogout의 메모이제이션이 정상적으로 동작하도록 수정되었다.


메모이제이션 성능 측정

마지막으로 메모이제이션의 성능상 이점을 측정하기 위해 profiling 을 사용한다.


참조