https://reactjs.org/docs/code-splitting.html - 번역 글

Bundling(번들링)

대부분의 React 앱은 Webpack 이나 Browserify 와 같은 도구를 사용하여 "번들 된"파일을 갖게 됩니다. 번들링은 가져온 파일을 따라 하나의 파일, 즉 "번들"으로 병합하는 프로세스입니다. 이 번들은 웹 페이지에 포함되어 한 번에 전체 앱을로드 할 수 있습니다.

예시

// app.js
import { add } from './math.js';

console.log(add(16, 26)); // 42
// math.js
export function add(a, b) {
  return a + b;
}

번들

function add(a, b) {
  return a + b;
}

console.log(add(16, 26)); // 42

노트
번들은 이보다 훨씬 많은 내용을 갖고 있게 됩니다.

React Create App, Next.js, Gatsby 또는 이와 유사한 도구를 사용한는 경우 내부에 포함된 Webpack를 사용하여 앱을 번들로 제공할 수 있습니다.

그렇지 않은 경우 번들 설정을 직접해야 합니다. 관련 내용은 Webpack문서의 설치시작 설명서를 참고 하세요.


Code Splitting(코드 스플릿팅)

번들링은 훌륭하지만 앱이 커짐에 따라 번들도 커집니다. 특히 큰 third-party 라이브러리를 포함하는 경우. 번들에 포함된 코드를 주의깊게 확인해야 실수로 커진 앱으로 인해 로드시간이 오래 걸리는 문제를 방지 합니다.

큰 번들로 묶이지 않으려면 번들을 code splitting 하는것이 좋습니다. code splitting 기능은 런타임시 동적으로 로드할 수 있는 여러 번들을 만들 수 있는 WebpackBrowserify(factor-bundle)와 같은 번들러에서 지원되는 기능입니다.

code splitting을 하면 사용자가 현재 필요로하는 것들만 lazy-load할 수 있으므로 앱의 성능을 크게 향상시킬 수 있습니다. 앱의 전체 코드 양을 줄이지는 않지만 사용자가 필요로하지 않은 코드를 로드하는 것을 피하고, 초기 페이지 로드시 필요한 코드만 받게 됩니다.


import()

어플리케이션에 code splitting을 도입하는 가장 좋은 방법은 동적 import()구문을 이용하는 것입니다.

before

import { add } from './math';

console.log(add(16, 26));

after

import("./math").then(math => {
  console.log(math.add(16, 26));
});

노트
동적 import구문은 현재 표준이 아닌 ECMAScript(JavaScript)의 제안단계 입니다. 가까운 미래에 받아 들여질 것으로 예상됩니다.

Webpack구문을 보게되면 자동으로 코드 스플릿팅이 시작 됩니다. Create React App을 사용한다면 이미 설정되어 있어 사용할 수있습니다. Next.js에서도 지원됩니다.

Webpack을 직접 설정하려면 code splitting에 관한 Webpack 가이드를 참고하세요. 대략적인 Webpack 설정은 이와 같이 합니다.

Babel을 사용할 때는 Babel 이 dynamic import 구문을 분석하게 하기위해서 babel-plugin-syntax-dynamic-import 플러그인이 필요합니다.


React.lazy

노트
React.lazySuspense는 아직 서버사이드 랜더링에 사용할 수 없습니다. 서버 사이드 랜더링된 애플리케이션에서 code splitting 기능을 사용하고 싶다면, Loadable Components를 추천합니다. 이것은 서버 사이드 랜더링과 번들 스플릿팅에 대한 가이드를 제공하고 있습니다.

React.lazy 함수를 사용하면 dynamic import를 사용하여 가져온 컴포넌트를 랜더링 할 수 있습니다.

before

import OtherComponent from './OtherComponent';

function MyComponent() {
  return (
    <div>
      <OtherComponent />
    </div>
  );
}

after

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <OtherComponent />
    </div>
  );
}

MyComponent 컴포넌트가 랜더링되면 OtherComponent컴포넌트를 포함한 번들이 자동으로 로드됩니다.

React 컴포넌트를 export default로 해석되는 Promise로 반환하고 React.lazy로 dynamic import()를 할때에는 함수 형태로 사용합니다.

Suspense

Suspense 컴포넌트를 사용한다면, MyComponent가 랜더링 될 때까지 동적으로 불러온 OtherComponent가 아직 로드가 되지 않은경우 로딩중과 같은 fallback content 표현이 가능합니다.

const OtherComponent = React.lazy(() => import('./OtherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

fallback 기능은 컴포넌트가 로드 될 때까지 기다리는 동안 랜더링하려는 모든 React요소에 적용가능합니다. Suspense 컴포넌트는 lazy 컴포넌트를 감쌉니다. 하나의 Suspense 컴포넌트로 여러 lazy 컴포넌트를 래핑할 수도 있습니다.

const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </div>
  );
}

Error boundaries

네트워크 장애로 인하여 다른 모듈이 로드에 실패한 경우 오류가 발생할 수 있습니다. 이런 경우 Error Boundaries를 이용하여 사용자 환경을 개선 및 복구 관리할 수 있습니다. Error Boundary를 만들어 lazy 컴포넌트를 감싸 네트워크 오류가 발생할때 오류 상태를 표시할 수 있습니다.

import MyErrorBoundary from './MyErrorBoundary';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

const MyComponent = () => (
  <div>
    <MyErrorBoundary>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </MyErrorBoundary>
  </div>
);

Route-based code spliting(라우터기반 코드 스플릿팅)

어플리케이션에서 code splitting을 적용할 위치를 결정하는 것은 까다로울 수 있습니다. 사용자 경험에 지장을 주지않고 번들을 균등하게 분할할 영역을 선택해야 합니다.

적용하기 좋은 곳은 route입니다. 웹페이지 로드 시간은 대부분 페이지 전환에 발생하며, 페이지를 한번에 렌더링하는 것이 대부분이므로 렌더링 중간에 사용자가 페이지와 상호 작용할 가능성은 거의 없습니다.

다음은 React Router와 같은 라이브러리를 사용한 어플리케이션에 React.lazy를 사용하여 경로 기반 code splitting하는 예제입니다.

import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';

const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));

const App = () => (
  <Router>
    <Suspense fallback={<div>Loading...</div>}>
      <Switch>
        <Route exact path="/" component={Home}/>
        <Route path="/about" component={About}/>
      </Switch>
    </Suspense>
  </Router>
);

Named Exports

React,lazy는 현재 export default만 지원합니다. 만약 named export를 사용한 경우 default로 이름을 재정의 하는 중간 모듈을 생성 할 수 있습니다. 아래와 같이 한다면 treeshaking이 계속 작동하고 사용하지 않는 컴포넌트를 가져오지 않게 됩니다.

// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));