221213 항해99 37일차 React.memo

요니링 컴터 공부즁·2022년 12월 14일
0

React.memo()

  • 유저들은 반응이 빠른 UI를 선호한다. 100ms 미만의 UI 응답 지연은 유저들이 즉시 느낄 수 있고, 100ms에서 300ms가 지연되면 이미 유저들은 상당한 지연으로 느낀다.

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

  • React는 먼저 컴퍼넌트를 렌더링(rendering) 한 뒤, 이전 렌더된 결과와 비교하여 DOM 업데이트를 결정한다. 만약 렌더 결과가 이전과 다르다면, React는 DOM을 업데이트한다.

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

  • 함수형 컴퍼넌트 MovieReact.memo()로 래핑 되어 있다.

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.memo(Movie)는 새로 메모이징된 컴퍼넌트인 MemoizedMovie를 반환한다. 한 가지 차이점을 제외하고 원래의 Movie 컴퍼넌트와 같은 결과를 나타낼 것이다.
  • MemoizedMovie의 렌더링 결과는 메모이징 되어있다. 만약 title이나 releaseData 같은 props가 변경 되지 않는다면 다음 렌더링 때 메모이징 된 내용을 그대로 사용하게 된다.
  • 메모이징 한 결과를 재사용 함으로써, React에서 리렌더링을 할 때 가상 DOM에서 달라진 부분을 확인하지 않아 성능상의 이점을 누릴 수 있다.

props 동등 비교 커스터마이징

  • React.memo()는 props 혹은 props의 객체를 비교할 때 얕은(shallow) 비교를 한다.
  • 비교 방식을 수정하고 싶다면 React.memo() 두 번째 매개변수로 비교함수를 만들어 넘겨주면 된다.
React.memo(Component, [areEqual(prevProps, nextProps)]);
  • areEqual(prevProps, nextProps) 함수는 prevProps와 nextProps가 같다면 true를 반환할 것이다.

  • Movieprops가 동일한지 수동으로 비교해보자.

function moviePropsAreEqual(prevMovie, nextMovie) {
  return (
    prevMovie.title === nextMovie.title &&
    prevMovie.releaseDate === nextMovie.releaseDate
  );
}

const MemoizedMovie2 = React.memo(Movie, moviePropsAreEqual);
  • moviePropsAreEqual() 함수는 이전 props와 현재 props가 같다면 true를 반환할 것이다.

언제 React.memo()를 써야할까
=> 같은 props로 렌더링이 자주 일어나는 컴퍼넌트

  • React.memo()는 함수형 컴퍼넌트에 적용되어 같은 props에 같은 렌더링 결과를 제공한다.

  • React.memo()를 사용하기 가장 좋은 케이스는 함수형 컴퍼넌트가 같은 props로 자주 렌더링 될거라 예상될 때이다.

  • 일반적으로 부모 컴퍼넌트에 의해 하위 컴퍼넌트가 같은 props로 리렌더링 될 때가 있다.

  • 여기 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가 새로운 숫자로 업데이트 될 때 마다 MoviewViewsRealtime 컴퍼넌트 또한 리렌더링 된다. 이때 Movie 컴퍼넌트 또한 title이나 releaseData가 같음에도 불구하고 리렌더링 된다.

  • 이때가 Movie 컴퍼넌트에 메모이제이션을 적용할 적절한 케이스다.

  • MovieViewsRealtime에 메모이징된 컴퍼넌트인 MemoizedMovie를 대신 사용해 성능을 향상해보자.

function MovieViewsRealtime({ title, releaseDate, views }) {
  return (
    <div>
      <MemoizedMovie title={title} releaseDate={releaseDate} />
      Movie views: {views}
    </div>
  );
}
  • title 혹은 releaseDate props가 같다면, React는 MemoizedMovie를 리렌더링 하지 않을 것이다. 이렇게 MovieViewsRealtime 컴퍼넌트의 성능을 향상할 수 있다.

  • 부모 컴퍼넌트가 자식 컴퍼넌트의 콜백 함수를 정의한다면, 새 함수가 암시적으로 생성될 수 있다. 이것은 메모이제이션을 막을 수 있다.

  • Logout 컴퍼넌트는 콜백 proponLogout을 갖는다.

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>
  );
}
  • 동일한 username 값이 전달되더라도, MemoizedLogout은 새로운 onLogout 콜백 때문에 리렌더링을 하게 된다.
  • 메모이제이션이 중단되게 되는 것이다.
  • 이 문제를 해결하려면 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을 사용해보자.

참조:
React.memo() 현명하게 사용하기

0개의 댓글