개발/리액트

[리액트] React.memo를 이용해 성능 최적화를 해보자

핸디(Handy) 2022. 1. 3. 11:38

React.memo가 필요한가?

어느 때와 같이 기획문서를 보고( 실은 기획자가 없어서 뇌리에 스치는 게 기획) 컴포넌트를 만들고 있었습니다.

이번엔 차트와 관련된 컴포넌트를 구현하고 있어서, Plotly 라이브러리를 활용해 미리 만들어놨던 컴포넌트에 테스트를 붙이고 구현하고 푸시를 했습니다.

문제 인식 | 성능이 구려

푸시를 하면 일단 기분이 좋기 때문에 완성된 모습을 뿌듯하게 쳐다보고 있는데 자꾸 버벅이는 느낌이 들어 바로 파고들어가 봅니다.

콘솔 창을 열고 확인을 해보니 같은 요청이 여러번 날아가는 것을 확인했습니다. 데브툴로 봐도 같은 prop임을 확인했습니다.

같은 요청 & 같은 데이터 --> 여러번 랜더링 -> 잘못된 구조 --> 최적화라는 뇌피셜 로직이 수행되었고 바로 어떤 컴포넌트인지 확인을 해봅니다.

무엇인가 보니, Material UI의 Accordion에서 사용하는 컴포넌트에서 요청이 날아감을 확인했습니다.

문제 발견 | MUI

 

React Accordion component - MUI

The accordion component allows the user to show and hide sections of related content on a page.

mui.com

컴포넌트를 어떻게 고칠까 잠깐 생각하다가, 매번 같은 요청이 날라가서 랜더링이 반복되는 것이니 React Query를 통해 요청을 캐싱할까 하다가 고민하다가 요청을 캐싱해봐야 해당 컴포넌트의 요청이 날아가고 다시 랜더링 되는 이슈는 이것으로 해결되지 않아 컴포넌트를 캐싱하는 React.memo를 적용하게 되었습니다.

 


 

React.memo란 무엇인가?

React.memo(이하 memo)는 성능 최적화를 위해 사용하는 고차 컴포넌트(High Order Component)로 React의 최상위 API중에 하나입니다. 

만약 특정 컴포넌트가 동일한 Props로 동일한 결과를 렌더링해낸다면, React.memo를 호출하고 결과를 메모이징하도록 래핑 하여 경우에 따라 성능 향상을 누릴 수 있습니다.

즉, React는 컴포넌트를 렌더링 하지 않고 마지막으로 렌더링된 결과를 재사용합니다.

React.memo는 props변화에만 영향을 주며, React.memo로 감싸진 함수 컴포넌트 구현에 useState 또는 useContext Hook을 사용하면, 여전히 state나 context가 변할 때 다시 렌더링 됩니다.

따라서 제가 생각하기로 컴포넌트 내부에서 특정 로직 없이 표현만 하는 컴포넌트 적합하다고 생각합니다.( 저의 경우에서는, 차트 컴포넌트에 memo를 적용했습니다)

사용방법 | 코드

//공식문서상 사용법
const MyComponent = React.memo(function MyComponent(props) {
  /* props를 사용하여 렌더링 */
});

// 일반적인 사용법
const MyComponent = (props) => {
  /* props를 사용하여 렌더링 */
}
export default React.memo(MyComponent);

또한 props의 변화는 얕은 비교만 수행하기 때문에 다른 비교 로직이 필요하다면 추가적인 비교 함수를 제공할 수 도 있습니다. ( 따라서 깊은 비교가 필요한 배열, 객체 등의 경우에 적용할 수 있습니다)

사용방법 | 비교로직

//별도 비교함수 제공
const MyComponent = (props) => {
  /* props를 사용하여 렌더링 */
}
const areEqual = (prevProps, nextProps) => {
  /*
  nextProp가 prevProps와 동일한 값을 가지면 true를 반환하고, 그렇지 않다면 false를 반환
  */
}
export default React.memo(MyComponent, areEqual);

사용방법 | 공식문서

 

React 최상위 API – React

A JavaScript library for building user interfaces

ko.reactjs.org

 


 

프로젝트에 어떻게 사용했는가?

일단 이미지로 한번 확인해보겠습니다.

memo 적용 전

 

Accordion를 Toggle 할 때마다 card?action=Query 요청이 4개씩 날아가는 게 보입니다. 또한 요청이 끝나고 차트 컴포넌트에서 다시 데이터를 요청하는 과정이 필요해서 차트 대신 차트 아이콘이 잠깐씩 노출됩니다.

이제 간단히 차트 컴포넌트에 memo를 붙여봅니다.

memo 적용 후

이제 처음 진입할 때만 랜더링이 되고 이후에 다시 요청이 날아가지 않는 모습을 확인할 수 있습니다. 또한 메모이징된 컴포넌트를 사용하다 보니 다시 차트를 그리는 로직까지 완료되어 차트 아이콘이 더 이상 보이지 않습니다.

 


 

React.memo 언제 쓰면 좋은가?

결론부터 말하자면 같은 props으로 랜더링이 자주 일어나는 컴포넌트, 무겁고 큰 연산이 있는 컴포넌트에 적용하면 됩니다.

저의 경우(차트 컴포넌트)에는 같은 props(차트 query)로 랜더링이 자주 일어나는데 해당 컴포넌트는 차트를 다시 그리는 연산(무겁고 큰 연산)을 포함하고 있습니다. 그래서 딱 적절한 예시가 아닐까 생각합니다.

그리고 이 외에 컴포넌트의 리랜더링을 방지하게 위해 React.memo를 쓰는 것을 지양합시다. 가끔씩 코드를 보다 보면 모든 컴포넌트에 React.memo를 적용한 경우를 볼 수 있습니다. 아직까지 큰 문제를 경험해보진 못했지만

캐싱, 메모이징이 모든 경우에 정답이 될 수 없음을 자잘한 경험을 통해 깨달았습니다. 그러니 필요한 곳에 필요한 컴포넌트에만 확실히 알고 사용하는 게 좋을 듯합니다.

추가적으로 문서를 하나 공유하고 싶습니다. 시간 될 때 읽어보세요

추가 자료

 

Use React.memo() wisely

React.memo() increases the performance of functional components by preventing useless renderings. But such performance tweaks must be applied wisely.

dmitripavlutin.com