안녕하세요, 소플입니다.

리액트로 규모가 큰 애플리케이션을 개발하다보면 점점 애플리케이션 최적화가 필요한 상황을 마주하게 됩니다.

이 때 다양한 부분에서 최적화를 적용해볼 수 있는데, 그 중에서도 렌더링 최적화는 꽤 중요하다고 할 수 있습니다.

하지만 이러한 렌더링 최적화 작업을 일일이 개발자가 하나하나 찾아서 하기란 쉽지 않습니다.

이번 매거진에서는 리액트 앱의 느린 렌더링을 찾아주는 React Scan이라는 도구에 대해 살펴보도록 하겠습니다.


React Scan 소개

React Scanmillion이라는 VS Code extension을 만든 개발자 Aiden Bai가 개발한 오픈소스 도구입니다.

참고로 million은 리액트 앱에서 느린 코드를 빠르게 찾고 고칠 수 있게 해주는 VS Code extension이라고 보면 됩니다.

million

million이 VS Code extension 형태로 최적화에 도움을 주는 도구라면,

React Scan은 아래와 같이 최적화가 필요한 컴포넌트를 브라우저에서 시각적으로 표시해주는 도구입니다.

React Scan

이러한 시각적인 단서를 통해 렌더링이 느린 컴포넌트를 빠르게 찾아내고 최적화를 진행할 수 있습니다.

다만, React Scan이 최적화까지 알아서 해주는 것은 아니고, 성능 최적화가 필요한 컴포넌트를 파악하는데만 도움을 주는 것입니다.

그리고 앞에서 소개해드린 million을 활용하면 컴포넌트 렌더링 최적화에 도움을 받을 수 있습니다.


왜 렌더링 최적화가 필요할까?

리액트 컴포넌트는 기본적으로 state가 변경되거나 props가 변경되었을 때 재렌더링이 발생합니다.

하지만 이러한 재렌더링이 발생하는 모든 상황을 개발자가 개발 과정에서 일일이 다 확인하기란 쉽지 않습니다.

특히 props의 경우에는 값이 아닌 참조로 비교하는데,

이로 인해 렌더링을 빠르게 처리할 수 있다는 장점도 있지만, 불필요한 렌더링이 발생할 가능성도 있습니다.

실제로 GitHub, Twitter, Instagram과 같은 대규모 앱을 수백 명의 엔지니어가 개발하면서도 완전히 최적화하지 못하는 경우가 있다고 합니다.

props로 인해 불필요한 렌더링이 발생하는 대표적인 예시로는,

아래와 같이 props콜백 함수객체 값을 직접 전달하는 경우가 있습니다.

<ExpensiveComponent
    style={{ color: 'green' }}
    onClick={() => alert('hello')}
/>

위 코드에서는 style 객체와 onClick 함수가 매 렌더링마다 새롭게 생성되고,

이를 ExpensiveComponentprops로 전달하게 되면서 불필요한 렌더링이 발생하게 됩니다.

이러한 모든 부분을 개발자가 다 확인하면서 개발하기란 쉽지 않기 때문에 렌더링 최적화가 필요하다고 할 수 있습니다.


렌더링 최적화를 위한 기존의 도구들

리액트 앱의 렌더링 최적화 이슈는 오래전부터 있었기 때문에, 이러한 문제를 해결하기 위한 다양한 도구들도 등장했습니다.

1. <Profiler> 컴포넌트 (링크 🔗)

리액트에서 공식적으로 제공하는 <Profiler> 컴포넌트는 특정 컴포넌트 트리의 렌더링 성능을 측정할 수 있게 해주는 컴포넌트입니다.

아래와 같이 성능을 측정하고 싶은 컴포넌트를 감싸는 형태로 사용하면 되고,

이 때 감싸고 있는 컴포넌트 트리에서 렌더링이 발생할 때마다 onRender() 콜백 함수가 호출됩니다.

<Profiler id="App" onRender={onRender}>
  <App />
</Profiler>

이러한 <Profiler> 컴포넌트의 단점은 개발자가 직접 코드를 작성해야 하기 때문에,

성능을 측정하려는 컴포넌트마다 일일이 코드를 작성해야 한다는 점입니다.

2. Why Did You Render (WDYR) (링크 🔗)

WDYR는 개발 환경에서 리액트 컴포넌트가 재렌더링되는 이유를 파악할 수 있게 도와주는 도구입니다.

WDYR는 아래와 같이 사용하면 됩니다.

import React from 'react';

if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React);
}

const MyComponent = React.memo(() => {
  // 컴포넌트 코드
});

MyComponent.whyDidYouRender = true; // 디버깅 활성화

그러면 콘솔에 아래와 같이 해당 컴포넌트가 재렌더링 되는 이유가 출력됩니다.

WDYR 로그

이러한 WDYR의 단점은 시각적인 표시가 부족하다는 점입니다.

콘솔 로그를 통해서 보는 것이 직관적이지도 않고 불편한 부분이 있기 때문이죠.

3. React Developer Tools (링크 🔗)

React Developer Tools는 리액트 개발자들이라면 대부분 사용하는 개발자 도구입니다.

React Developer Tools를 설치하면 아래와 같이 개발자 도구에서 Profiler 탭을 볼 수 있습니다.

이 Profiler 기능을 통해서 렌더링에 걸린 시간과 컴포넌트가 재렌더링 되는 이유를 확인할 수 있습니다.

React Developer Tools Profiler

React Developer Tools는 완성된 애플리케이션 형태로 제공되는 것이기 때문에 프로그래밍 가능한 API가 없다는 단점이 있습니다.

그리고 이러한 기존 도구들의 단점을 해결하기 위해 등장한 것이 바로 React Scan입니다.


React Scan 특징

그렇다면 기존 도구들과 차별되는 React Scan의 특징은 뭘까요?

React Scan의 주요 특징은 아래와 같습니다.

간편한 통합

React Scan을 사용하기 위해서는 <Profiler> 컴포넌트처럼 직접 코드를 작성하지 않아도 되고,

<script> 태그, npm 패키지, CLI 명령어 등의 다양한 방법으로 쉽게 사용할 수 있습니다.

예를 들면, 아래와 같이 터미널에서 npx 명령어를 사용해서 React Scan을 사용할 수 있습니다.

npx react-scan@latest http://localhost:3000

성능 문제 시각화

React Scan은 느린 렌더링을 유발하는 컴포넌트를 자동으로 감지하고, 화면 상에 하이라이트하여 문제를 신속히 파악할 수 있게 해줍니다.

앞에 나왔던 "Why Did You Render"에서 시각적인 표시가 부족하다는 단점을 해결하는 부분이라고 할 수 있습니다.

React Scan

다양한 환경 지원

React Scan은 특정 프레임워크에 종속된 것이 아니라,

Next.js, Vite, Create React App 등 여러 프레임워크와 호환됩니다.

확장 가능성

React Scan은 클라이언트 측 API와 함께 제공되며,

이러한 특징은 React Developer Tools의 단점을 해결하는 부분이라고 할 수 있습니다.

또한 React Scan은 문제 해결에 필요한 보고서 생성, 렌더링 로그 기록 등의 다양한 옵션도 제공합니다.

위와 같은 특징을 가진 React Scan은 특히 초기 최적화 단계에서 유용하며,

개발 환경에 바로 통합하여 성능 병목을 쉽게 찾아낼 수 있도록 설계되었습니다.


React Scan 사용 방법

React Scan은 다양한 방법으로 사용할 수 있습니다.

먼저 로컬에서 작동 중인 애플리케이션을 스캔할 경우에는 아래와 같이 npx 명령어를 사용하면 됩니다.

npx react-scan@latest http://localhost:3000

그리고 특정 웹사이트를 스캔할 경우에는 아래와 같이 해당 웹사이트의 주소를 넣으면 됩니다.

npx react-scan@latest https://react.dev

npx 명령어를 실행하면 새로운 크롬 브라우저가 실행되고,

아래와 같이 React Scan이 작동하는 것을 볼 수 있습니다.

FrontOverflow에도 최적화 해야 할 곳이 많이 보이는군요🤣

React Scan 작동 모습

만약 스캔을 하기 위해서 매번 스크립트를 쓰는 것이 귀찮다면,

아래와 같이 package.json파일에 scan 스크립트를 추가하면 됩니다.

{
  "scripts": {
    "dev": "next dev",
    "scan": "next dev & npx react-scan@latest localhost:3000"
  }
}

또한 아래와 같이 <script> 태그를 사용하여 적용할 수도 있습니다.

<!-- 다른 scripts보다 먼저 import 해야함 -->
<script src="https://unpkg.com/react-scan/dist/auto.global.js"></script>

마지막으로 Next.js의 App Router에 연동하기 위해서는,

먼저 아래와 같이 RootLayout<script> 태그를 사용하여 React Scan을 추가합니다.

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <script src="https://unpkg.com/react-scan/dist/auto.global.js" async />
        {/* rest of your scripts go under */}
      </head>
      <body>{children}</body>
    </html>
  )
}

그리고 이후에 아래와 같이 scan() 함수를 import 해서 클라이언트 환경에서만 실행되도록 해주면 됩니다.

import { scan } from 'react-scan'; // react보다 먼저 import 해야함
import React from 'react';

if (typeof window !== 'undefined') {
  scan({
    enabled: true,
    log: true, // 로그는 콘솔에 출력됨 (기본 값: false)
  });
}

🔗 참고 링크


이번 매거진에서는 리액트 앱의 느린 렌더링을 찾아주는 React Scan 라이브러리에 대해 알아보았습니다.

연동하는 방법이 굉장히 간단하기 때문에, 각자 개발 중인 프로젝트에 한 번씩 적용해보시면 좋을 것 같습니다.

그럼 저는 다음에 또 유익한 글로 찾아뵙겠습니다!

지금까지 소플이었습니다. 감사합니다 😀

지금 가입하고 새로운 매거진을 이메일로 받아보세요!

Copyright ⓒ Soaple. All rights reserved.