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 함수는 애플리케이션의 구성요소를 별도의 자바스크립트 청크로 분리하고 추가 작업을 거의 하지 않는 별도의 방법을 기본적으로 제공합니다. 그런 다음 Suspense 구성요소와 결합하면 로드 상태를 처리할 수 있습니다.

서스펜스

사용자에게 대량의 JavaScript 페이로드를 전달할 때 발생하는 문제는 특히 기기 및 네트워크 연결이 약한 경우 페이지 로드를 완료하는 데 걸리는 시간입니다. 이러한 이유로 코드 분할과 지연 로드가 매우 유용합니다.

그러나 코드 분할 구성요소를 네트워크를 통해 가져올 때는 사용자가 경험해야 하는 약간의 지연이 항상 발생하므로 유용한 로드 상태를 표시하는 것이 중요합니다. React.lazySuspense 구성요소와 함께 사용하면 이 문제를 해결하는 데 도움이 됩니다.

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

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

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

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

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

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

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

  • 사이트를 미리 보려면 View App을 누른 다음 Fullscreen 전체 화면을 누릅니다.
  • `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 문서에서는 react-router와 함께 Suspense를 사용하는 방법을 보여줍니다.
  2. 사이트의 페이지에서 특정 사용자 상호작용 (예: 버튼 클릭)에서만 렌더링되는 큰 구성요소를 확인합니다. 이러한 구성요소를 분할하면 JavaScript 페이로드가 최소화됩니다.
  3. 화면에 표시되지 않고 사용자에게 중요하지 않은 내용은 분할하는 것이 좋습니다.