React

[React] React.memo를 이용한 성능 최적화

킹우현 2023. 7. 18. 12:30

앞서 React Profiler를 사용하여 리액트 앱의 성능을 측정해보는 시간을 가졌습니다.

 

모든 기능을 하나의 컴포넌트에 구현한 A.js와 여러 기능들을 여러 개의 컴포넌트로 분리한 B.js 로 구분하여 두 컴포넌트의 성능을 비교해보았는데, 이번 시간에는 React.memo를 사용하여 렌더링 성능을 개선시켜보도록 하겠습니다 :)

 

 

[React] React Developer Tools / React Profiler로 앱 성능 측정하기

React Developer Tools 확장 프로그램 https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=ko React Developer Tools Adds React debugging tools to the Chrome Developer Tools. Created from revision 035a41c4e

woohyun-king.tistory.com

 

React.memo 란 ?

React.memo는 Memoization(메모이제이션) 기법으로 동작하며, 고차 컴포넌트(Higher Order Component, HOC)이다.

 

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

 

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

메모이제이션 (Memoization) 이란? ✨
- 계산된 값을 자료구조에 저장하고 이후 같은 계산을 반복하지 않고 자료구조에서 꺼내 재사용하는 것
- 주어진 입력값에 대한 결과를 저장함으로써 같은 입력값에 대해 함수가 한 번만 실행 되는 것을 보장하는 기법

 

다시 말해, 컴포넌트가 props로 동일한 결과를 렌더링하면, React.memo를 호출하고 결과를 메모이징(Memoizaing) 하도록 래핑하여 경우에 따라 성능 향상을 할 수 있다.

 

React.memo는 props 변화에만 영향을 준다. 즉, 함수 컴포넌트 안에서 구현한 state나 context가 변할 때는 재렌더링된다.

 

props가 갖는 복잡한 객체에 대하여 얕은 비교만을 수행하는 것이 기본 동작이다. 다른 비교 동작을 원한다면, 두번째 인자로 별도의 비교 함수를 제공하면 된다.

 

현재 B 컴포넌트의 문제점

현재 앱에서 B 컴포넌트는 B, List, ListItem, Message 컴포넌트로 나눠져 있습니다. 이렇게 나눠준 이유는 재사용성을 위해서도 이지만 각 컴포넌트의 렌더링의 최적화를 위해서 이기도 합니다.

 

예를 들어서 Input에서 글을 타이핑을 할 때 원래는 Message 컴포넌트와 그 State 값을 가지고 있는 App 컴포넌트만 렌더링이 되어야 하는데 현재는 상관이 없는 다른 부분(List, ListItem)까지 리렌더링 되고 있습니다.

 

React.memo로 문제점 해결하기

리렌더링을 방지하고자 하는 컴포넌트를 React.memo()로 래핑해주면 됩니다. 이제는 props의 변화가 없는 List, ListItem 컴포넌트는 리렌더링이 되지 않는 것을 확인할 수 있습니다.

 

1번째 "h"를 타이핑 해서 Re Rendering 했을 때

 

App Component : 3.7ms
A Component: 3.2ms
B Component: 0.1ms

 

A Component Rendering TimeB Component Rendering Time

 

위처럼 React Profiler에서도 B 컴포넌트의 렌더링 속도가 엄청나게 향상된 것을 확인할 수 있습니다.

 

React Memo가 props를 비교하는 방법

React.memo()props 혹은 props의 객체를 비교할 때 얕은(shallow) 비교를 합니다.

 

React.memo Props 비교 방식 수정하기

function MyComponent(props) {
  /* props를 사용하여 렌더링 */
}
function areEqual(prevProps, nextProps) {
  /*
  nextProps가 prevProps와 동일한 값을 가지면 true를 반환하고, 그렇지 않다면 false를 반환
  */
}
export default React.memo(MyComponent, areEqual);

비교 방식을 원하는 대로 수정하고 싶다면 React.memo()의 두 번째 매개변수로 비교함수를 넣어주시면 됩니다.

 

React.memo 를 사용을 지양해야하는 상황

렌더링 될 때 props가 변경되는 경우가 대부분인 컴포넌트를 생각해보면, 메모이제이션 기법의 이점을 얻기 힘듭니다. props가 자주 변하는 컴포넌트를 React.memo()로 래핑 할지라도, React는 두 가지 작업을 리 렌더링 할 때마다 수행합니다.


1.
이전 props와 다음 props의 동등 비교를 위해 비교 함수를 수행합니다.

2. 비교 함수는 거의 항상 false를 반환할 것이기 때문에, React는 이전 렌더링 내용과 다음 렌더링 내용을 비교합니다.

 

이때, 비교 함수의 결과는 대부분 false를 반환하기에 props 비교는 불필요하게 됩니다. 🚨

 

React.memo는 리렌더링을 막기 위한 도구가 아닌 성능 개선을 위한 도구

React에서는 성능 개선(최적화)을 위한 하나의 도구로 메모이제이션을 사용합니다.

대부분의 상황에서 React는 메모이징 된 컴포넌트의 리 렌더링을 피할 수 있지만, 렌더링을 막기 위해 메모이제이션에 너무 의존하면 안됩니다(버그 유발 가능성이 있습니다).

 

React.memo() 정리 및 결론

  • useMemo처럼 Memoization 기법으로 동작
  • 인자로 받는 props가 변화할 때 재렌더링되고, 이전과 동일하면 예전 결과값을 반환
  • React.memo로 감싸진 함수 컴포넌트 안에서 구현한 state가 변화하면 재렌더링 된다.
  • React.memo에서 props가 객체로 받아진다면 얕은 비교로 기본동작한다.(비원시타입 동일)
  • React.memo에서 다른 비교 동작을 원한다면 두번째 인자에 별도의 비교함수를 만들어야 한다.

 

결론적으로 리액트에서 렌더링 성능 최적화 위해선 React 컴포넌트를 분리하며, React.memo()를 사용하면 됩니다.

하지만 React.memo 사용은 항상 좋은 것은 아니기에 profiler를 이용해서 성능상 이점이 있는지 확인 후 사용해야 합니다.