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)된 내용을 재사용한다.
함수형 컴퍼넌트 Movie
가 React.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
가 변경 되지 않는다면 다음 렌더링 때 메모이징 된 내용을 그대로 사용하게 된다.props 동등 비교 커스터마이징
React.memo()
는 props 혹은 props의 객체를 비교할 때 얕은(shallow) 비교를 한다.React.memo()
두 번째 매개변수로 비교함수를 만들어 넘겨주면 된다.React.memo(Component, [areEqual(prevProps, nextProps)]);
areEqual(prevProps, nextProps) 함수는 prevProps와 nextProps가 같다면 true를 반환할 것이다.
Movie
의 props
가 동일한지 수동으로 비교해보자.
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>
);
}
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
컴퍼넌트는 콜백 prop
인 onLogout
을 갖는다.
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을 사용해보자.