웹사이트 검색

React 앱의 성능을 향상시키는 5가지 팁


React 앱이 다소 느리게 느껴지나요? 보이는 것 때문에 Chrome DevTools에서 "페인트 플래시\를 켜는 것이 두렵습니까? 다음 5가지 성능 팁을 시도해 보세요!

이 기사에는 React 개발을 위한 5가지 성능 팁이 포함되어 있습니다. 이 목차를 사용하여 이 문서를 빠르게 탐색할 수 있습니다.

도움말로 이동

  • memoPureComponent 사용
  • 익명 함수 피하기
  • 객체 리터럴 피하기
  • React.lazyReact.Suspense 사용
  • 잦은 마운트/언마운트 방지

메모와 PureComponent 사용

아래의 이 단순한 React 앱을 고려하십시오. props.propA만 값을 변경하면 다시 렌더링될 것이라고 생각하십니까?

import React from 'react';

const MyApp = (props) => {
  return (
    <div>
      <ComponentA propA={props.propA}/>
      <ComponentB propB={props.propB}/>
    </div>
  );
};

const ComponentA = (props) => {
  return <div>{props.propA}</div>
};

const ComponentB = (props) => {
  return <div>{props.propB}</div>
};

대답은 예입니다! 이는 MyApp이 실제로 재평가(또는 재렌더링 😏)되고 가 거기에 있기 때문입니다. 따라서 자체 소품이 변경되지 않았더라도 상위 구성 요소로 인해 다시 렌더링됩니다.

이 개념은 클래스 기반 React 구성 요소의 render 메서드에도 적용됩니다.

React 작성자는 이것이 항상 원하는 결과가 아닐 수 있으며 다시 렌더링하기 전에 기존 소품과 새 소품을 비교하여 쉽게 성능을 향상시킬 수 있음을 인식했습니다. 이것이 본질적으로 React.PureComponent 입니다. 하도록 설계되었습니다!

기능적 구성 요소와 함께 memo를 사용합시다(다음에 클래스 기반 구성 요소를 살펴보겠습니다).

import React, { memo } from 'react';

// 🙅♀️
const ComponentB = (props) => {
  return <div>{props.propB}</div>
};

// 🙆♂️
const ComponentB = memo((props) => {
  return <div>{props.propB}</div>
});

그게 다야! memo() 함수로 감싸기만 하면 됩니다. 이제 propB가 실제로 값을 변경하는 경우에만 부모가 다시 렌더링되는 횟수에 관계없이 다시 렌더링됩니다!

PureComponent를 살펴보겠습니다. 기본적으로 memo와 동일하지만 클래스 기반 구성 요소용입니다.

import React, { Component, PureComponent } from 'react';

// 🙅♀️
class ComponentB extends Component {
  render() {
    return <div>{this.props.propB}</div>
  }
}

// 🙆♂️
class ComponentB extends PureComponent {
  render() {
    return <div>{this.props.propB}</div>
  }
}

이러한 성능 향상은 거의 너무 쉽습니다! React 구성 요소가 과도한 다시 렌더링에 대한 이러한 내부 보호 장치를 자동으로 포함하지 않는 이유가 궁금할 수 있습니다.

실제로 memoPureComponent에는 숨겨진 비용이 있습니다. 이러한 도우미는 이전/새 소품을 비교하기 때문에 실제로 자체 성능 병목 현상이 될 수 있습니다. 예를 들어 props가 매우 크거나 React 구성 요소를 props로 전달하는 경우 이전/새 props를 비교하는 데 비용이 많이 들 수 있습니다.

프로그래밍 세계에서 은총알은 드물다! 그리고 memo/PureComponent도 예외가 아닙니다. 확실히 측정되고 사려 깊은 방식으로 시승하고 싶을 것입니다. 경우에 따라 얼마나 많은 계산 비용을 절감할 수 있는지 놀라게 될 수 있습니다.

React Hooks의 경우 불필요한 계산 작업을 방지하는 유사한 방법으로 useMemo를 확인하십시오.

익명 함수 피하기

구성 요소의 본체 내부에 있는 함수는 일반적으로 이벤트 핸들러 또는 콜백입니다. 많은 경우 익명 함수를 사용하고 싶은 유혹을 느낄 수 있습니다.

import React from 'react';

function Foo() {
  return (
    <button onClick={() => console.log('boop')}> // 🙅♀️
      BOOP
    </button>
  );
}

익명 함수는 (const/let/var를 통해) 식별자가 할당되지 않기 때문에 이 기능 구성 요소가 필연적으로 다시 렌더링될 때마다 영구적이지 않습니다. 이로 인해 "명명된 함수\를 사용할 때 단일 메모리를 한 번만 할당하는 대신 이 구성 요소가 다시 렌더링될 때마다 JavaScript가 새 메모리를 할당합니다.

import React, { useCallback } from 'react';

// Variation 1: naming and placing handler outside the component 
const handleClick = () => console.log('boop');
function Foo() {
  return (
    <button onClick={handleClick}>  // 🙆♂️
      BOOP
    </button>
  );
}

// Variation 2: "useCallback"
function Foo() {
  const handleClick = useCallback(() => console.log('boop'), []);
  return (
    <button onClick={handleClick}>  // 🙆♂️
      BOOP
    </button>
  );
}

useCallback은 익명 함수의 함정을 피하는 또 다른 방법이지만 앞서 다룬 React.memo와 유사한 장단점이 있습니다.

클래스 기반 구성 요소를 사용하면 솔루션이 매우 쉽고 실제로 단점이 없습니다. React에서 핸들러를 정의하는 데 권장되는 방법입니다.

import React from 'react';

class Foo extends Component {
  render() {
    return (
      <button onClick={() => console.log('boop')}>  {/* 🙅♀️ */}
        BOOP
      </button>
    );
  }
}

class Foo extends Component {
  render() {
    return (
      <button onClick={this.handleClick}>  {/* 🙆♂️ */}
        BOOP
      </button>
    );
  }
  handleClick = () => {  // this anonymous function is fine used like this
    console.log('boop');
  }
}

객체 리터럴을 피하십시오

이 성능 팁은 익명 함수에 대한 이전 섹션과 유사합니다. 개체 리터럴에는 영구 메모리 공간이 없으므로 구성 요소가 다시 렌더링될 때마다 구성 요소가 메모리에 새 위치를 할당해야 합니다.

function ComponentA() {
  return (
    <div>
      HELLO WORLD
      <ComponentB style={{  {/* 🙅♀️ */}
        color: 'blue',     
        background: 'gold'
      }}/>
    </div>
  );
}

function ComponentB(props) {
  return (
    <div style={this.props.style}>
      TOP OF THE MORNING TO YA
    </div>
  )
}

가 다시 렌더링될 때마다 새 객체 리터럴이 메모리 내에서 "생성\되어야 합니다. 또한 이는 가 실제로 수신되고 있음을 의미합니다. 다른 style 개체. memoPureComponent를 사용해도 여기에서 다시 렌더링되는 것을 막을 수 없습니다 😭

이 팁은 style props에만 적용되는 것은 아니지만 일반적으로 객체 리터럴이 React 구성 요소에서 무의식적으로 사용되는 경우입니다.

이는 객체의 이름을 지정하여 쉽게 수정할 수 있습니다(물론 구성 요소 본체 외부!).

const myStyle = {  // 🙆♂️
  color: 'blue',     
  background: 'gold'
};
function ComponentA() {
  return (
    <div>
      HELLO WORLD
      <ComponentB style={myStyle}/>
    </div>
  );
}

function ComponentB(props) {
  return (
    <div style={this.props.style}>
      TOP OF THE MORNING TO YA
    </div>
  )
}

React.lazy 및 React.Suspense 사용

React 앱을 빠르게 만드는 부분은 코드 분할을 통해 수행할 수 있습니다. 이 기능은 React.lazy 및 React.Suspense와 함께 React v16에 도입되었습니다.

모르는 경우 코드 분할의 개념은 JavaScript 클라이언트 소스(예: React 앱 코드)가 더 작은 청크로 분할되고 이러한 청크만 지연 방식으로 로드하는 것입니다. 코드 분할이 없으면 단일 번들이 매우 커질 수 있습니다.

- bundle.js (10MB!)

코드 분할을 사용하면 번들에 대한 초기 네트워크 요청이 훨씬 작아질 수 있습니다.

- bundle-1.js (5MB)
- bundle-2.js (3MB)
- bundle-3.js (2MB)

초기 네트워크 요청은 "단지\ 5MB를 다운로드하면 되고 최종 사용자에게 흥미로운 것을 보여주기 시작할 수 있습니다. 처음에 머리글과 바닥글만 필요한 블로그 웹사이트를 상상해 보십시오. 일단 로드되면 요청을 시작합니다. 실제 블로그 게시물이 포함된 두 번째 번들 코드 분할이 편리한 초보적인 예입니다. 👏👏👏

코드 분할은 React에서 어떻게 수행됩니까?

이렇게 사용한다면.

다음은 lazySuspense가 구현된 간단한 예입니다.

import React, { lazy, Suspense } from 'react';
import Header from './Header';
import Footer from './Footer';
const BlogPosts = React.lazy(() => import('./BlogPosts'));

function MyBlog() {
  return (
    <div>
      <Header>
      <Suspense fallback={<div>Loading...</div>}>
        <BlogPosts />
      </Suspense>
      <Footer>
    </div>
  );
}

fallback 소품에 주목하십시오. 두 번째 번들 청크가 로드되는 동안 사용자에게 즉시 표시됩니다(예: ).

React Suspense를 사용한 코드 분할에 대한 이 훌륭한 기사를 확인하십시오 🐊

빈번한 마운트/마운트 해제 방지

우리는 삼항 문(또는 이와 유사한 것)을 사용하여 구성 요소를 사라지게 만드는 데 여러 번 익숙합니다.

import React, { Component } from 'react';
import DropdownItems from './DropdownItems';

class Dropdown extends Component {
  state = {
    isOpen: false
  }
  render() {
    <a onClick={this.toggleDropdown}>
      Our Products
      {
        this.state.isOpen
          ? <DropdownItems>
          : null
      }
    </a>
  }
  toggleDropdown = () => {
    this.setState({isOpen: !this.state.isOpen})
  }
}

가 DOM에서 제거되기 때문에 브라우저에서 다시 그리기/리플로우를 유발할 수 있습니다. 특히 다른 HTML 요소가 이동하는 경우 비용이 많이 들 수 있습니다.

이를 완화하려면 구성 요소를 완전히 마운트 해제하지 않는 것이 좋습니다. 대신 CSS opacity를 0으로 설정하거나 CSS visibility를 "none\으로 설정하는 것과 같은 특정 전략을 사용할 수 있습니다. 성능 비용을 발생시키지 않고 효과적으로 사라집니다.