React.lazy 및 Suspense를 사용한 코드 분할

사용자에게 필요한 것보다 많은 코드를 전송할 필요는 없으므로 번들을 분할하여 이러한 상황이 발생하지 않도록 하세요.

React.lazy 메서드를 사용하면 동적 가져오기를 사용하여 구성요소 수준에서 React 애플리케이션을 코드 분할하기가 쉽습니다.

import React, { lazy } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));

const DetailsComponent = () => (
  <div>
    <AvatarComponent />
  </div>
)

이것이 왜 유용할까요?

대규모 React 애플리케이션은 일반적으로 여러 구성요소, 유틸리티 메서드, 서드 파티 라이브러리로 구성됩니다. 필요할 때만 애플리케이션의 다른 부분을 로드하려고 노력하지 않으면 사용자가 첫 번째 페이지를 로드하는 즉시 하나의 큰 JavaScript 번들이 사용자에게 전송됩니다. 이는 페이지 성능에 상당한 영향을 미칠 수 있습니다.

React.lazy 함수는 애플리케이션의 구성요소를 별도의 JavaScript 청크로 분리하는 기본 제공 방법을 제공하며, 작업량이 매우 적습니다. 그런 다음 Suspense 구성요소와 결합할 때 로드 상태를 처리할 수 있습니다.

서스펜스

사용자에게 큰 JavaScript 페이로드를 전송할 때의 문제는 특히 성능이 약한 기기와 네트워크 연결에서 페이지가 로드되는 데 걸리는 시간입니다. 이러한 이유로 코드 분할과 지연 로딩이 매우 유용합니다.

하지만 코드 분할 구성요소가 네트워크를 통해 가져올 때는 항상 약간의 지연이 발생하므로 유용한 로드 상태를 표시하는 것이 중요합니다. Suspense 구성요소와 함께 React.lazy를 사용하면 이 문제를 해결할 수 있습니다.

import React, { lazy, Suspense } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));

const renderLoader = () => <p>Loading</p>;

const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
  </Suspense>
)

Suspensefallback 구성요소를 허용하므로 React 구성요소를 로드 상태로 표시할 수 있습니다. 다음 예는 작동 방식을 보여줍니다. 아바타는 버튼을 클릭할 때만 렌더링되며, 이때 정지된 AvatarComponent에 필요한 코드를 가져오는 요청이 이루어집니다. 그동안 대체 로드 구성요소가 표시됩니다.

여기에서 AvatarComponent를 구성하는 코드는 작기 때문에 로딩 스피너가 짧은 시간 동안만 표시됩니다. 특히 네트워크 연결이 약한 경우 큰 구성요소는 로드하는 데 훨씬 더 오래 걸릴 수 있습니다.

작동 방식을 더 잘 보여주기 위해 다음을 참고하세요.

  • 사이트를 미리 보려면 앱 보기를 누릅니다. 그런 다음 전체 화면 전체 화면을 누릅니다.
  • `Control+Shift+J` (또는 Mac의 경우 `Command+Option+J`)를 눌러 DevTools를 엽니다.
  • 네트워크 탭을 클릭합니다.
  • 기본적으로 제한 없음으로 설정된 제한 드롭다운을 클릭합니다. Fast 3G를 선택합니다.
  • 앱에서 Click Me 버튼을 클릭합니다.

이제 로드 표시기가 더 오래 표시됩니다. AvatarComponent을 구성하는 모든 코드가 별도의 청크로 가져와지는 것을 확인하세요.

하나의 chunk.js 파일이 다운로드되고 있음을 보여주는 DevTools 네트워크 패널

여러 구성요소 일시중지

Suspense의 또 다른 기능은 모두 지연 로드되더라도 여러 구성요소의 로드를 일시중지할 수 있다는 것입니다.

예를 들면 다음과 같습니다.

import React, { lazy, Suspense } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));

const renderLoader = () => <p>Loading</p>;

const DetailsComponent = () => (
  <Suspense fallback={renderLoader()}>
    <AvatarComponent />
    <InfoComponent />
    <MoreInfoComponent />
  </Suspense>
)

이는 단일 로드 상태만 표시하면서 여러 구성요소의 렌더링을 지연하는 매우 유용한 방법입니다. 모든 구성요소의 가져오기가 완료되면 사용자에게 모든 구성요소가 동시에 표시됩니다.

다음 삽입을 통해 이를 확인할 수 있습니다.

이 기능이 없으면 로딩이 지연되거나 UI의 여러 부분이 각각 자체 로딩 표시기를 사용하여 순차적으로 로드되는 문제가 발생하기 쉽습니다. 이로 인해 사용자 환경이 더 어색하게 느껴질 수 있습니다.

로드 실패 처리

Suspense를 사용하면 네트워크 요청이 백그라운드에서 이루어지는 동안 임시 로드 상태를 표시할 수 있습니다. 하지만 어떤 이유로든 이러한 네트워크 요청이 실패하면 어떻게 될까요? 오프라인 상태이거나 웹 앱에서 서버 재배포 후 더 이상 사용할 수 없는 오래된 버전이 지정된 URL을 지연 로드하려고 시도하고 있을 수 있습니다.

React에는 오류 경계를 사용하여 이러한 유형의 로드 실패를 정상적으로 처리하는 표준 패턴이 있습니다. 문서에 설명된 대로 React 구성요소는 수명 주기 메서드 static getDerivedStateFromError() 또는 componentDidCatch() (또는 둘 다)를 구현하는 경우 오류 경계 역할을 할 수 있습니다.

지연 로딩 실패를 감지하고 처리하려면 Suspense 구성요소를 오류 경계 역할을 하는 상위 구성요소로 래핑하면 됩니다. 오류 경계의 render() 메서드 내에서 오류가 없으면 하위 요소를 그대로 렌더링하고, 문제가 있으면 맞춤 오류 메시지를 렌더링할 수 있습니다.

import React, { lazy, Suspense } from 'react';

const AvatarComponent = lazy(() => import('./AvatarComponent'));
const InfoComponent = lazy(() => import('./InfoComponent'));
const MoreInfoComponent = lazy(() => import('./MoreInfoComponent'));

const renderLoader = () => <p>Loading</p>;

class ErrorBoundary extends React.Component {
  constructor(props) {
    super(props);
    this.state = {hasError: false};
  }

  static getDerivedStateFromError(error) {
    return {hasError: true};
  }

  render() {
    if (this.state.hasError) {
      return <p>Loading failed! Please reload.</p>;
    }

    return this.props.children;
  }
}

const DetailsComponent = () => (
  <ErrorBoundary>
    <Suspense fallback={renderLoader()}>
      <AvatarComponent />
      <InfoComponent />
      <MoreInfoComponent />
    </Suspense>
  </ErrorBoundary>
)

결론

React 애플리케이션에 코드 분할을 어디서부터 적용해야 할지 잘 모르겠다면 다음 단계를 따르세요.

  1. 경로 수준에서 시작합니다. 경로는 분할할 수 있는 애플리케이션 지점을 식별하는 가장 간단한 방법입니다. React 문서에서는 Suspensereact-router와 함께 사용하는 방법을 보여줍니다.
  2. 특정 사용자 상호작용 (예: 버튼 클릭)에서만 렌더링되는 사이트 페이지의 대형 구성요소를 식별합니다. 이러한 구성요소를 분할하면 JavaScript 페이로드가 최소화됩니다.
  3. 화면에 표시되지 않고 사용자에게 중요하지 않은 다른 항목을 분할하는 것이 좋습니다.