태그 보관물: 코드 최적화

React Code Splitting

코드 분할

번들링은 훌륭하지만 앱이 커지면 번들도 커져서 로드 속도가 느려집니다. 프로젝트 초기라면 괜찮습니다. 하지만 프로젝트 규모가 커지고 최적화가 필요한 단계에서 Code Splitting을 한다면 서비스 퍼포먼스가 좋아지기 때문에 사용자들에게 보다 나은 사용자 경험을 줄 수 있습니다.

방법 #1. React.lazy

React.lazy 함수를 사용하면 동적 import를 사용해서 컴포넌트를 렌더링 할 수 있습니다.
(React.lazy는 React 16.6버전 부터 지원합니다.)

Before

import OtherComponent from "./OtherComponent";

After

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

With Suspense

lazy 컴포넌트는 Suspense 컴포넌트 하위에서 렌더링되어야 하며, Suspense는 lazy 컴포넌트가 로드되길 기다리는 동안 로딩 화면과 같은 예비 컨텐츠를 보여줄 수 있게 해줍니다.

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

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

하나의 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>
  );
}

Route-based 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>
);

코드 Build 결과

dist 디렉토리를 보면 라우팅 단위로 파일이 분리되어 생성된것을 확인 할 수 있습니다.

react-app
 dist/
  - index.html
  - main.b1234.js (contains Appcomponent and bootstrap code)
  - home.bc4567.js (contains Home)
  - about.bc4567.js (contains About)

/** index.html **/
<head>
  <div id="root"></div>
  <script src="main.b1234.js"></script>
</head>

흔히 발생할 수 있는 실수

import 하는 페이지 내부에 사용중인 컴포넌트가 사용하지 않는 다른 컴포넌트와 index.js에 export를 편의상 묶어 사용하는 경우가 있습니다. 이때 컴포넌트 import시 객체 디스트럭쳐링을 사용할 경우 내부적으로 사용하지 않는 컴포넌트도 함께 로드되기 때문에 코드 분할 시 큰 효과를 보지 못합니다. 그렇기 때문에 실사용 컴포넌트만 개별 임포트 하는것이 중요합니다.

// 예로, PageA를 코드 분할하려고 합니다.
const PageA = React.lazy(() => import("./PageA"));
function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <PageA />
      </Suspense>
    </div>
  );
}

// PageA.js 에선 Button 컴포넌트를 사용하려고 import하고 있습니다.
// X 사용하지 않는 다른 컴포넌트도 함께 로드됩니다.
import { Button } from "./components"; 
// O 개별 컴포넌트로 로드해야 불필요한 컴포넌트가 불려 번들 사이즈가 커지는것을 방지할 수 있어요.
import Button from "./components/Button";

function PageA() {
  return (
    <div>
      <h1>PageA</h1>
      <Button>OK</Button>
    </div>
  );
}

// components/index.js
export { default as Loader } from "./common/Loader";
export { default as Button } from "./common/OtherSetting";
export { default as InsideSearch } from "./common/InsideSearch";

 

방법 #2. Loadable Components

React.lazy와 Suspense는 서버 사이드 렌더링을 지원하지 않습니다. 코드 분할을 클라이언트, 서버사이드 모두 가능하도록 만들고 싶다면 Loadable Components를 사용하면됩니다. (React 공식 문서에서 권장하는 방법)

React.lazy와 비교

Library Suspense SSR Library splitting import(./${value})
React.lazy
@loadable/component

Install

npm install @loadable/component

# or use yarn
yarn add @loadable/component

loadable Import

import loadable from "@loadable/component";

const OtherComponent = loadable(() => import("./OtherComponent"));

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

With Suspense

React Suspense를 지원하여 함께 사용할 수 있습니다.

import React, { Suspense } from "react";
import { lazy } from "@loadable/component";

const OtherComponent = lazy(() => import("./OtherComponent"));

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

Full dynamic import

다이나믹하게 import() 하는 Loadable Component를 만들어서 코드의 반복을 줄일 수 있습니다.

import loadable from "@loadable/component";

const AsyncPage = loadable((props) => import(`./${props.page}`));

function MyComponent() {
  return (
    <div>
      <AsyncPage page="Home" />
      <AsyncPage page="Contact" />
    </div>
  );
}

번외, React-loadable

React-loadable 모듈은 React 공식 문서에서도 오랫동안 권장하던 방법이었습니다. 그러나 현재는 더 이상 유지 관리되지 않으며 Webpack v4 + 및 Babel v7 +와 호환되지 않는 부분들이 있습니다.
그래서 React.lazy 또는 Loadable components로 변경하는것을 추천합니다.

참고