효율적인 React 구성 요소: React 성능 최적화 가이드

게시 됨: 2022-03-11

React는 도입된 이후로 프론트엔드 개발자가 웹 앱 구축에 대해 생각하는 방식을 바꾸었습니다. 가상 DOM을 사용하여 React는 UI 업데이트를 가능한 한 효율적으로 만들어 웹 앱을 더 빠르게 만듭니다. 그러나 중간 크기의 React 웹 앱이 여전히 성능이 좋지 않은 경향이 있는 이유는 무엇입니까?

글쎄요, 단서는 React를 어떻게 사용하고 있는지에 있습니다.

React와 같은 최신 프론트 엔드 라이브러리는 마법처럼 앱을 더 빠르게 만들지 않습니다. 개발자는 React가 작동하는 방식과 구성 요소가 구성 요소 수명 주기의 다양한 단계를 통해 어떻게 작동하는지 이해해야 합니다.

React를 사용하면 구성 요소가 렌더링되는 방법과 시기를 측정하고 최적화하여 제공해야 하는 많은 성능 향상을 얻을 수 있습니다. 그리고 React는 이를 쉽게 만드는 데 필요한 도구와 기능만 제공합니다.

구성 요소의 render-diff 프로세스를 최적화하여 React 앱의 속도를 높입니다.

이 React 튜토리얼에서는 React 구성 요소의 성능을 측정하고 최적화하여 훨씬 더 성능이 좋은 React 웹 앱을 구축하는 방법을 배웁니다. 또한 몇 가지 JavaScript 모범 사례가 React 웹 앱이 훨씬 더 유창한 사용자 경험을 제공하는 데 어떻게 도움이 되는지 배우게 됩니다.

반응은 어떻게 작동합니까?

최적화 기술에 대해 알아보기 전에 React가 어떻게 작동하는지 더 잘 이해해야 합니다.

React 개발의 핵심에는 간단하고 분명한 JSX 구문과 가상 DOM을 빌드하고 비교할 수 있는 React의 기능이 있습니다. 출시 이후 React는 다른 많은 프론트엔드 라이브러리에 영향을 미쳤습니다. Vue.js와 같은 라이브러리도 가상 DOM 아이디어에 의존합니다.

React 작동 방식은 다음과 같습니다.

각 React 애플리케이션은 루트 구성 요소로 시작하며 트리 형태의 많은 구성 요소로 구성됩니다. React의 컴포넌트는 수신한 데이터(props 및 state)를 기반으로 UI를 렌더링하는 "함수"입니다.

이것을 F 로 기호화할 수 있습니다.

 UI = F(data)

사용자는 UI와 상호 작용하여 데이터를 변경합니다. 상호 작용에 버튼 클릭, 이미지 탭핑, 목록 항목 드래그, AJAX 요청 API 호출 등이 포함되는지 여부에 관계없이 이러한 모든 상호 작용은 데이터만 변경합니다. UI를 직접 변경하지 않습니다.

여기서 데이터는 웹 애플리케이션의 상태를 정의하는 모든 것이지 데이터베이스에 저장한 것이 아닙니다. 프런트 엔드 상태의 비트(예: 현재 선택된 탭 또는 확인란이 현재 선택되어 있는지 여부)도 이 데이터의 일부입니다.

이 데이터에 변경 사항이 있을 때마다 React는 구성 요소 기능을 사용하여 UI를 다시 렌더링하지만 가상으로만 다음을 수행합니다.

 UI1 = F(data1) UI2 = F(data2)

React는 가상 DOM의 두 버전에 비교 알고리즘을 적용하여 현재 UI와 새 UI 간의 차이를 계산합니다.

 Changes = Diff(UI1, UI2)

그런 다음 React는 브라우저의 실제 UI에 UI 변경 사항만 적용합니다.

구성 요소와 관련된 데이터가 변경되면 React는 실제 DOM 업데이트가 필요한지 여부를 결정합니다. 이를 통해 React는 DOM 노드를 생성하고 필요 이상으로 기존 노드에 액세스하는 것과 같이 브라우저에서 잠재적으로 비용이 많이 드는 DOM 조작 작업을 피할 수 있습니다.

이러한 반복되는 구성 요소의 비교 및 ​​렌더링은 모든 React 앱에서 React 성능 문제의 주요 원인 중 하나가 될 수 있습니다. diffing 알고리즘이 효과적으로 조정되지 않는 React 앱을 빌드하면 전체 앱이 반복적으로 렌더링되는 결과가 실망스러울 정도로 느려질 수 있습니다.

어디에서 최적화를 시작해야 합니까?

그러나 우리가 최적화하는 것이 정확히 무엇입니까?

초기 렌더링 프로세스 동안 React는 다음과 같은 DOM 트리를 빌드합니다.

React 구성 요소의 가상 DOM

데이터 변경의 일부가 주어지면 React가 하기를 원하는 것은 변경에 직접적으로 영향을 받는 구성 요소만 다시 렌더링하는 것입니다(나머지 구성 요소에 대한 diff 프로세스도 건너뛸 수 있음).

최적의 구성 요소 수를 렌더링하는 React

그러나 React가 하는 일은 다음과 같습니다.

모든 구성 요소를 렌더링하는 리소스를 낭비하는 반응

위의 이미지에서 모든 노란색 노드가 렌더링 및 비교되어 시간/계산 리소스가 낭비됩니다. 여기에서 주로 최적화 노력을 기울일 것입니다. 필요할 때만 render-diff로 각 구성 요소를 구성하면 이러한 낭비된 CPU 주기를 회수할 수 있습니다.

React 라이브러리의 개발자는 이것을 고려하여 우리가 할 수 있는 후크를 제공했습니다. 구성 요소 렌더링을 건너뛸 수 있을 때 React에 알릴 수 있는 기능입니다.

먼저 측정

Rob Pike는 프로그래밍 규칙 중 하나로 이를 다소 우아하게 표현합니다.

측정하다. 측정할 때까지 속도를 조정하지 말고 코드의 한 부분이 나머지 부분을 압도하지 않는 한 조정하지 마십시오.

앱 속도를 저하시킬 수 있다고 생각되는 코드 최적화를 시작하지 마십시오. 대신 React 성능 측정 도구가 길을 안내합니다.

React에는 이를 위한 강력한 도구가 있습니다. react-addons-perf 라이브러리를 사용하면 앱의 전체 성능에 대한 개요를 얻을 수 있습니다.

사용법은 매우 간단합니다.

 Import Perf from 'react-addons-perf' Perf.start(); // use the app Perf.stop(); Perf.printWasted();

이렇게 하면 구성 요소가 렌더링에 낭비되는 시간이 포함된 테이블이 인쇄됩니다.

렌더링에 시간을 낭비하는 구성 요소 테이블

이 라이브러리는 낭비되는 시간의 여러 측면을 별도로 인쇄하거나(예: printInclusive() 또는 printExclusive() 함수를 사용하여) 또는 DOM 조작 작업( printOperations() 함수를 사용하여)을 인쇄할 수 있는 다른 함수를 제공합니다.

벤치마킹 한 단계 더 나아가기

당신이 시각적인 사람이라면 react-perf-tool 이 당신에게 필요한 것입니다.

react-perf-toolreact-addons-perf 라이브러리를 기반으로 합니다. React 앱의 성능을 디버깅하는 보다 시각적인 방법을 제공합니다. 기본 라이브러리를 사용하여 측정값을 얻은 다음 그래프로 시각화합니다.

렌더링에 시간을 낭비하는 구성 요소의 시각화

종종 이것은 병목 현상을 발견하는 훨씬 더 편리한 방법입니다. 응용 프로그램에 구성 요소로 추가하여 쉽게 사용할 수 있습니다.

React가 구성 요소를 업데이트해야 합니까?

기본적으로 React는 가상 DOM을 실행하고 렌더링하고 트리의 모든 구성 요소에 대한 차이점을 props 또는 state의 변경 사항에 대해 비교합니다. 그러나 그것은 분명히 합리적이지 않습니다.

앱이 성장함에 따라 모든 작업에서 전체 가상 DOM을 다시 렌더링하고 비교하려고 하면 결국 속도가 느려집니다.

React는 개발자가 구성 요소를 다시 렌더링해야 하는지 여부를 나타내는 간단한 방법을 제공합니다. 여기서 shouldComponentUpdate 메소드가 작동합니다.

 function shouldComponentUpdate(nextProps, nextState) { return true; }

이 함수가 구성 요소에 대해 true를 반환하면 render-diff 프로세스가 트리거될 수 있습니다.

이것은 렌더-차이 프로세스를 제어하는 ​​간단한 방법을 제공합니다. 구성 요소가 다시 렌더링되는 것을 방지해야 할 때마다 함수에서 false 를 반환하기만 하면 됩니다. 함수 내에서 현재 및 다음 props 및 state 세트를 비교하여 재렌더링이 필요한지 여부를 결정할 수 있습니다.

 function shouldComponentUpdate(nextProps, nextState) { return nextProps.id !== this.props.id; }

React.PureComponent 사용하기

이 최적화 기술을 좀 더 쉽게 자동화하기 위해 React는 "순수한" 구성 요소를 제공합니다. React.PureComponent 는 얕은 prop 및 상태 비교를 사용하여 shouldComponentUpdate() 함수를 구현하는 React.Component 와 정확히 같습니다.

React.PureComponent 는 다음과 거의 동일합니다.

 class MyComponent extends React.Component { shouldComponentUpdate(nextProps, nextState) { return shallowCompare(this.props, nextProps) && shallowCompare(this.state, nextState); } … }

얕은 비교만 수행하므로 다음과 같은 경우에만 유용할 수 있습니다.

  • 소품 또는 상태에는 기본 데이터가 포함되어 있습니다.
  • 소품과 상태에는 복잡한 데이터가 있지만 언제 forceUpdate() 를 호출하여 구성 요소를 업데이트해야 하는지 알고 있습니다.

데이터를 변경할 수 없도록 만들기

React.PureComponent 를 사용할 수 있지만 복잡한 props 또는 state가 자동으로 변경되었을 때 이를 알리는 효율적인 방법이 있다면 어떻게 될까요? 이것은 불변 데이터 구조가 삶을 더 쉽게 만드는 곳입니다.

불변 데이터 구조를 사용하는 아이디어는 간단합니다. 복잡한 데이터가 포함된 개체가 변경될 때마다 해당 개체를 변경하는 대신 변경 내용이 포함된 해당 개체의 복사본을 만듭니다. 이렇게 하면 두 개체의 참조를 비교하는 것처럼 간단하게 데이터 변경 사항을 감지할 수 있습니다.

Object.assign 또는 _.extend (Underscore.js 또는 Lodash에서)를 사용할 수 있습니다.

 const newValue2 = Object.assign({}, oldValue); const newValue2 = _.extend({}, oldValue);

더 나아가 불변 데이터 구조를 제공하는 라이브러리를 사용할 수 있습니다.

 var map1 = Immutable.Map({a:1, b:2, c:3}); var map2 = map1.set('b', 2); assert(map1.equals(map2) === true); var map3 = map1.set('b', 50); assert(map1.equals(map3) === false);

여기서 Immutable.Map 은 Immutable.js 라이브러리에서 제공합니다.

맵이 set 메서드로 업데이트될 때마다 set 작업이 기본 값을 변경한 경우에만 새 맵이 반환됩니다. 그렇지 않으면 동일한 맵이 반환됩니다.

여기에서 불변 데이터 구조를 사용하는 방법에 대해 자세히 알아볼 수 있습니다.

더 많은 React 앱 최적화 기술

프로덕션 빌드 사용

React 앱을 개발할 때 정말 유용한 경고와 오류 메시지가 표시됩니다. 이를 통해 개발 중에 버그와 문제를 식별할 수 있습니다. 그러나 성능 대가를 치르게 됩니다.

React의 소스 코드를 살펴보면 if (process.env.NODE_ENV != 'production') 검사를 많이 볼 수 있습니다. React가 개발 환경에서 실행 중인 이러한 코드 덩어리는 최종 사용자에게 필요한 것이 아닙니다. 프로덕션 환경에서는 이 불필요한 코드를 모두 버릴 수 있습니다.

create-react-app 을 사용하여 프로젝트를 부트스트랩했다면 이 추가 코드 없이 npm run build 를 실행하여 프로덕션 빌드를 생성할 수 있습니다. Webpack을 직접 사용하는 경우 webpack -p ( webpack --optimize-minimize --define process.env.NODE_ENV="'production'" 과 동일)를 실행할 수 있습니다.

초기에 함수 바인딩

렌더 함수 내에서 구성 요소의 컨텍스트에 바인딩된 함수를 보는 것은 매우 일반적입니다. 이것은 종종 이러한 함수를 사용하여 자식 구성 요소의 이벤트를 처리하는 경우입니다.

 // Creates a new `handleUpload` function during each render() <TopBar onUpload={this.handleUpload.bind(this)} /> // ...as do inlined arrow functions <TopBar onUpload={files => this.handleUpload(files)} />

이것은 render() 함수가 모든 렌더에서 새로운 함수를 생성하도록 합니다. 동일한 작업을 수행하는 훨씬 더 좋은 방법은 다음과 같습니다.

 class App extends React.Component { constructor(props) { super(props); this.handleUpload = this.handleUpload.bind(this); } render() { … <TopBar onUpload={this.handleUpload} /> … } }

여러 청크 파일 사용

단일 페이지 React 웹 앱의 경우 우리는 종종 모든 프론트엔드 JavaScript 코드를 하나의 축소된 파일로 묶습니다. 이것은 중소 규모의 웹 앱에 적합합니다. 그러나 앱이 성장하기 시작하면 번들로 제공되는 이 JavaScript 파일을 브라우저에 전달하는 것 자체가 시간 소모적인 프로세스가 될 수 있습니다.

Webpack을 사용하여 React 앱을 빌드하는 경우 코드 분할 기능을 활용하여 빌드된 앱 코드를 여러 "청크"로 분리하고 필요에 따라 브라우저에 전달할 수 있습니다.

분할에는 리소스 분할과 주문형 코드 분할의 두 가지 유형이 있습니다.

리소스 분할을 사용하면 리소스 콘텐츠를 여러 파일로 분할할 수 있습니다. 예를 들어 CommonsChunkPlugin을 사용하여 공통 코드(예: 모든 외부 라이브러리)를 자체 "청크" 파일로 추출할 수 있습니다. ExtractTextWebpackPlugin을 사용하면 모든 CSS 코드를 별도의 CSS 파일로 추출할 수 있습니다.

이러한 종류의 분할은 두 가지 면에서 도움이 됩니다. 브라우저가 덜 자주 변경되는 리소스를 캐시하는 데 도움이 됩니다. 또한 브라우저가 병렬 다운로드를 활용하여 잠재적으로 로드 시간을 줄이는 데 도움이 됩니다.

Webpack의 더 주목할만한 기능은 주문형 코드 분할입니다. 이를 사용하여 요청 시 로드할 수 있는 청크로 코드를 분할할 수 있습니다. 이렇게 하면 초기 다운로드를 작게 유지하여 앱을 로드하는 데 걸리는 시간을 줄일 수 있습니다. 그런 다음 브라우저는 애플리케이션에서 필요할 때 요청 시 다른 코드 청크를 다운로드할 수 있습니다.

여기에서 Webpack 코드 분할에 대해 자세히 알아볼 수 있습니다.

웹 서버에서 Gzip 활성화

React 앱의 번들 JS 파일은 일반적으로 매우 크므로 웹 페이지를 더 빠르게 로드하려면 웹 서버(Apache, Nginx 등)에서 Gzip을 활성화할 수 있습니다.

최신 브라우저는 모두 HTTP 요청에 대해 Gzip 압축을 지원하고 자동으로 협상합니다. Gzip 압축을 활성화하면 전송된 응답의 크기를 최대 90%까지 줄일 수 있으므로 리소스를 다운로드하는 시간을 크게 줄이고 클라이언트의 데이터 사용량을 줄이며 페이지의 첫 번째 렌더링 시간을 개선할 수 있습니다.

압축을 활성화하는 방법에 대한 웹 서버 설명서를 확인하십시오.

  • Apache: mod_deflate 사용
  • Nginx: ngx_http_gzip_module 사용

Eslint-plugin-react 사용

거의 모든 JavaScript 프로젝트에 ESLint를 사용해야 합니다. 반응도 다르지 않습니다.

eslint-plugin-react 를 사용하면 장기적으로 코드에 도움이 되고 잘못 작성된 코드로 인해 발생하는 많은 일반적인 문제와 문제를 피할 수 있는 React 프로그래밍의 많은 규칙에 적응해야 합니다.

React 웹 앱을 다시 빠르게 만들기

React를 최대한 활용하려면 도구와 기술을 활용해야 합니다. React 웹 앱의 성능은 구성 요소의 단순성에 있습니다. 압도적인 render-diff 알고리즘은 앱의 성능을 실망스럽게 만들 수 있습니다.

앱을 최적화하기 전에 React 구성 요소가 작동하는 방식과 브라우저에서 렌더링되는 방식을 이해해야 합니다. React 수명 주기 메서드는 구성 요소가 불필요하게 다시 렌더링되는 것을 방지하는 방법을 제공합니다. 이러한 병목 현상을 제거하면 사용자에게 합당한 앱 성능을 얻을 수 있습니다.

React 웹 앱을 최적화하는 더 많은 방법이 있지만 필요할 때만 업데이트하도록 구성 요소를 미세 조정하면 최고의 성능 향상을 얻을 수 있습니다.

React 웹 앱 성능을 어떻게 측정하고 최적화합니까? 아래 의견에 공유하십시오.

관련 항목: React Hooks를 사용한 Stale-while-revalidate 데이터 가져오기: 가이드