React memoizing - React.memo

addiescode·2020년 7월 27일
1

Advanced React Usage

목록 보기
3/3

컴포넌트 성능 최적화 방법 - React.memo

React.memo는 리액트에서 memoizing을 위해 사용되는 React HOC(Higher-order components)이다. memoizing은 컴퓨터 프로그래밍에서 function call에 대한 캐싱(caching) 결과를 재사용함으로써 컴퓨터가 그 함수를 호출하기 위해 다시 연산하는 것을 방지하는 기법인데, React에서는 render메소드로 캐싱 결과를 얻는다. 즉, 리액트에서는 최초 render 사이클의 실행 결과를 캐싱하게 되는데 이때 props, state, class properties, function variables등의 input이 똑같을 때 캐싱 결과를 재사용하는 컨셉으로 memoizing이 가능하다.

데이터가 많아지면 어플리케이션이 느려지는 것을 체감할 수 있는 정도로 지연이 발생하게 되는데, React에서 이런 성능을 개선하고 최적화 시키는 방법은 두가지가 있다.

첫번째는, 클래스형 컴포넌트의 lifecycle method중 하나인 componentShouldUpdate() 메소드이고 두번째는 React hooks를 사용하는 경우 React.memo API를 사용하는 방법이다.

방법은 어떤 컴포넌트를 작성하느냐에 따라 다르지만 하는 일은 똑같다.
componet의 props를 가지고 얕은 비교 (shallow comparision)를 해서 re-render가 발생해야하는지, 해야하지 않는지 판단하는 것이다.

이 React.memo를 사용하는 방법 또한 두 가지인데, 공통적으로 React.memo로 작성하거나/작성될 컴포넌트를 감싸주는 방식이다.

  1. 컴포넌트 작성 시 function을 React.memo로 감싸준다.

	const MyComponent = React.memo(function WrappedComp(props){
...
});
  1. 컴포넌트를 작성하고 그 컴포넌트를 React.memo로 감싸준다.
const WrappedComp = (props) => {
 ...
}

export default React.memo(WrappedComp);

선택적으로, React.memo함수에서는 두번째 인자에 비교할 때 refresh가 필요한 조건을 적어줄 수 있는 comparision function을 전달해줄 수 있다. 이때 이 function은 prevProps와 nextProps를 인자로 받는다.

React.memo 사용의 장점

이런 과정을 거쳐, 컴포넌트가 React.memo로 래핑될 때 React는 최초 render()에서 컴포넌트를 렌더링하고 결과를 memoizing하게된다. 그리고, 다음 렌더링이 일어날 때 props가 같다면 memoizig된 내용을 재사용한다. 그래서 이 재사용으로 React가 리렌더링할때 가상 DOM에서 달라진 부분을 확인하지않아 성능상의 이점을 누릴 수 있는게 장점이다.

언제 React.memo를 사용할까?

React.memo()는 함수형 컴포넌트에 적용되어 같은 props에 같은 렌더링 결과를 제공하므로 같은 props로 자주 렌더링 될 것이라고 예상될 때 사용하면 가장 좋다.

예를 들어, Movie 컴포넌트가 MovieViewsRealtime 부모 컴포넌트에서 props를 받아오는 상황인데, 몇개의 props들은 똑같은 내용을 가지고 있다면 메모이징을 활용하면 좋다.


const MovieViewsRealtime = (props) => {
	return(
			<div>
				<Movie title={props.title} releaseDate={props.releasedDate} />
				views: {props.views}
			</div>
	);
}

<MovieViewsRealtime views={0} title="same title" releasedDate="same date" />
<MovieViewsRealtime views={10} title="same title" releasedDate="same date" />
<MovieViewsRealtime views={30} title="same title" releasedDate="same date" />
  

이런 상황이라면 title과 releasedDate는 똑같이 렌더되는데도 views가 업데이트되면서 MovieViewsRealtime또한 리렌더된다. 이때가 Movie컴포넌트에 메모이징을 활용하면 좋은 케이스이다. 메모이징을 한다면 title혹은 releasedDate가 같을때 React는 MovieViewsRealtime을 리렌더하지않아 컴포넌트의 리렌더링 성능을 최적화해준다.

콜백함수와의 비교(useCallback)

부모 컴포넌트에서 자식 컴포넌트의 콜백함수를 정의하게 되면 새 함수가 암시적으로 생성되는데, 이때 함수의 동등성이라는 함정때문에 메모이징을 적용할 때 리렌더를 할 때마다 부모함수가 다른 콜백함수의 인스턴스를 넘길수도있게된다. 이를 방지하기 위해 매번 동일한 콜백 인스턴스를 보존시켜야하는데 이건 useCallback을 이용해 보존시킬 수 있다.

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>
  );
}

MyApp이라는 부모 컴포넌트에서 콜백함수를 지정해주었고, 자식 컴포넌트에서 이를 props으로 받아 쓰게 된다면 리렌더를 할 때마다 다른 콜백 함수 인스턴스를 넘길 가능성이 생긴다. 그렇기때문에 memoizing이 되지 않을 수 있다. 이를 방지하려면 해당 props로 받은 부모의 콜백함수를 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>
  );
}

// 항상 동일한 콜백 인스턴스를 반환한다. 즉, 위의 케이스의 memoization이 정상작동한다.

0개의 댓글