저는 최근에 리액트 앱의 성능을 향상시켜야 하는 업무를 담당하게 되었습니다. 이 글은 제가 그 리액트 성능 최적화를 진행하고 기록한 글입니다.
1단계 - 성능이 저하되기 시작하는 시나리오 찾기
성능이 저하되는 모든 시나리오를 파악하려면 앱에서 다양한 작업을 엄격하게 수행하고 성능이 저하되기 시작하는 시기를 계속 모니터링해야 합니다. 이를 위한 직접적인 방법은 없고, 유일한 방법은 많은 사람들이 앱을 사용하고 자신의 경험을 보고하는 것입니다. 또 다른 방법은 충분히 많은 더미 데이터를 생성하고 UI에 모든 데이터를 로드하여 어떻게 작동하는지 확인하는 것입니다. 또한 모든 시나리오를 한 번에 파악할 수는 없습니다. 시나리오를 계속 검색한 후 아래 단계를 수행하여 각 시나리오의 성능을 향상시킬 수 있습니다.
2단계 - 디버그를 통해 문제 원인 찾기
다음 단계에서는 디버깅을 통하여 성능 지연이나 저하를 초래하는 원인이 무엇인지 확인합니다. 이를 위해 개발자 도구에서 프로파일러를 사용할 수 있으며, 특정 작업에서 다시 렌더링되는 모든 컴포넌트를 강조 표시할 수도 있습니다. 이 두 가지가 가장 도움이 되는 이유는 무엇이 다시 렌더링되는지 프로파일러가 그 이유를 알려주기 때문입니다.
또 프로파일러는 어떤 컴포넌트가 렌더링에 얼마나 많은 시간이 소요되었는지와 당신의 앱이 준비 상태로 가는 데 얼마나 많은 주기가 필요한지 알려줍니다.
또한 중첩된 루프를 보고 완료하는데 많은 시간이 걸리는지 확인하려면 javascript의 console.time() 메서드를 사용할 수 있습니다. 저의 시나리오에서, 저는 사용중인 리액트 컨텍스트가 주요 문제를 야기하고 있다는 몇 가지 사전 정보를 제공받았습니다.
3단계 - 아래 기술들을 사용하여 성능을 개선
이 섹션에는 앱의 성능을 향상시키는 데 도움이 된 작업을 나열하겠습니다.
1. 컴포넌트를 다시 렌더링할 필요가 없는 상태 변수를 제거했습니다.
두 가지 컨텍스트와 약 10-15개의 상태 변수가 있었습니다. 이것의 문제는 setState로 인해 컨텍스트가 다시 렌더링될 때마다 컨텍스트가 사용된 모든 자식 컴포넌트들이 다시 렌더링하게 된다는 것입니다. 컴포넌트를 다시 렌더링할 이유가 없는 상태 변수를 모두 제거했으며, 앱 전체에서 파생될 수 있거나 사용되지 않는 컨텍스트 프로바이더에게 값으로 전달된 변수도 모두 제거했습니다. 이것은 중요한 학습이었습니다. 우리는 모든 것을 컨텍스트에 두는 경향이 있습니다. 반면에 여러분은 여러분이 정말로 필요로 하는 것을 추가해야 합니다.
2. 컨텍스트를 올바른 위치에서 사용
몇 가지 컴포넌트에서 컨텍스트를 호출했지만 실제로 컨텍스트의 어떤 속성도 사용하지 않고 있었습니다. 오히려 그것을 Props로 자식 컴포넌트에 전달하고 있었습니다. 이로 인해 컨텍스트에서 다시 렌더링하면 컴포넌트A가 다시 렌더링되고 컴포넌트 B, C, D가 모두 다시 렌더링되기 때문에 많은 리렌더링이 발생합니다. 컴포넌트D에서만 컨텍스트가 필요했기 때문에 하위 구성 요소로 직접 컨텍스트를 이동했습니다. 컨텍스트 변수를 볼 때마다 이 작업을 자식 컴포넌트에 Props로 전달했습니다.
3. Null 과 빈 값 체크 추가
부모 및 자식 내부의 데이터가 필요한 자식 컴포넌트를 렌더링하는 것을 보았습니다. 컴포넌트는 데이터가 있는지 확인하는 기능을 추가합니다. 이 접근 방식에는 문제가 없지만, 자식 구성 요소가 Effects를 사용하거나 다른 API를 호출하는 경우 null/빈값 검사를 부모 컴포넌트로 이동하는 것이 좋습니다. 하위 컴포넌트에는 값이 없으므로 DOM에 렌더링할 필요가 없습니다. 이렇게 하면 앱이 자식 컴포넌트를 렌더링하고 그 안에 있는 모든 Hooks와 API를 호출하기 위해 취했을 수 있는 모든 성능 히트가 세이브됩니다.
4. 코드 리팩토링
제가 수행한 일반적인 단계는 특히 데이터가 조작되거나 배열에 추가되거나 중첩된 루프 등을 사용한 곳에서 작성된 코드를 이해하려고 시도하는 것이었습니다. Array를 의미가 있는 Map으로 변환하고, 값을 추가하지 않는 useEffect 종속성 Array에서 변수를 추가로 제거했으며, 마지막으로 의미가 없는 데이터에 대한 확인도 제거했습니다. 이 단계는 앱마다 다를 수 있으며 이미 작동 중인 것을 망가뜨리고 싶지 않기 때문에 매우 신중하게 수행해야 합니다. 따라서 이상적으로 먼저 논리를 이해하기 위해 가능한 한 깊이 분석해보고, 그 다음에 자신감이 생긴다면 계속하여 그것을 리팩터링하시기 바랍니다.
결과
위의 방법들을 통해서 저는 약 25-30개의 리렌더를 줄일 수 있었습니다. 성능이 낭비되는 시나리오에서 초기 페이지 로드를 몇 초 단축하고 응답 시간을 1~2초 단축했습니다. 그리고 저는 여전히 성능을 더 좋게 만들기 위해 일하고 있습니다.
마지막으로, 리액트 앱을 최적화할 수 있는 다른 많은 방법이 있습니다. 모든 앱에서 모두 작동하는 것은 아니며, 수행하는 모든 작업이 상당한 성능 향상을 가져오는 것도 아닙니다. 때로는 문제가 명확하고 해결하면 앱이 성능이 향상되는 경우도 있습니다. 다른 시나리오에서는 문제가 복잡하며 성능 향상을 가져오기가 어렵습니다. 하지만 전반적으로, 코드를 최적화하는 것은 즐거운 작업입니다. 이 작업을 통해 여러분은 많은 것을 배울 수 있을 것입니다.
코드를 리팩터링하는 데 시간을 할애하고 성능을 평가하는 데 시간을 할애하는 경우가 많습니다. 그렇지 않으면 나중에는 너무 늦어질 수 있기 때문입니다.
시간을 내어 글을 읽어주셔서 감사합니다.