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>
)

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

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

작동 방식을 더 잘 보여주려면 다음 단계를 따르세요.

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

이제 로드 표시기가 더 오래 표시됩니다. AvatarComponent를 구성하는 모든 코드가 별도의 청크로 가져오는 것을 볼 수 있습니다.

다운로드 중인 chunk.js 파일 1개를 보여주는 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에는 이러한 유형의 로드 실패를 적절하게 처리하기 위한 표준 패턴인 오류 경계를 사용합니다. 문서에 설명된 대로 수명 주기 메서드 static getDerivedStateFromError() 또는 componentDidCatch() 중 하나 (또는 둘 다)를 구현하는 모든 React 구성요소는 오류 경계 역할을 할 수 있습니다.

지연 로드 실패를 감지하고 처리하려면 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. 화면을 벗어나고 사용자에게 중요하지 않은 다른 것은 분할하는 것이 좋습니다.