코드 스플리팅(Code Splitting)과 React.lazy

sangjunpark520·2022년 12월 6일
0
post-thumbnail

코드 스플리팅은 무엇일까?

대부분의 리액트 파일들은 Webpack, Rollup, Browserify 등을 사용해서 파일들을 번들링 시킵니다. 그 다음에는 쪼개져 있었던 여러 개의 파일들을 하나로 합치는 과정을 거칩니다.

번들링하는 과정이 나쁜 것은 아닙니다. 그런데, 어플리케이션의 크기가 커지게 된다면 번들파일도 따라서 커진다는 사실을 잊어서는 안됩니다. 특히 굉장히 거대한 서드파티 라이브러리를 포함하고자 하는 경우에는 엄청나게 큰 파일을 맞닥뜨리게 될 것입니다. 따라서 이런 거대한 번들 파일을 막기 위해서는 번들 파일을 ‘스플리팅’하는 방식을 통해서 이러한 문제점을 해결할 수 있어야 합니다. 또한 코드 스플리팅을 하게 된다면 ‘lazy loading’을 도와준다는 장점이 있습니다.

Lazy Loading?

그렇다면 Lazy Loading이 무엇이길래 코드 스플리팅 과정을 거치면 좋다고 하는 것일까요? https://mathiasbynens.be/demo/img-loading-lazy 이 웹사이트를 보게 되신다면 알 것입니다. (Lazy Load와 관련하여 더 자세한 내용을 알고 싶은 경우에는 ‘https://web.dev/i18n/ko/browser-level-image-lazy-loading/’ 이 웹사이트를 참조해 주세요) 처음에 멈추었을 경우에는 이미지가 보여지는 곳이나 그 근처의 경우에만 부분적으로 로딩이 됩니다. 그런데, 스크롤을 내리게 되면 이에 따라 더 많은 이미지들을 내린 스크롤에 맞게 보여주게 됩니다. 무한 스크롤링 기법과도 어느 정도 연관성이 있는 웹 성능 최적화 기법입니다. 이 Lazy-Loading 기법을 사용하게 되면 앱의 성능을 정말로 크게 향상시킬 수 있습니다.

그렇다면 코드 스플리팅을 어떻게 적용시킬 수 있을까요? 첫 번째로 바로 ‘import()’ 구문을 살펴보기로 하겠습니다.

// before
import { add } from './math';
console.log(add(16, 26));

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

만약 웹팩이 다음의 코드를 맞닥뜨리게 된다면 자동적으로 어플리케이션의 코드 스플리팅을 자동적으로 진행하게 됩니다. 가장 대표적으로 Next.js나 CRA가 자동 코드 스플리팅을 지원합니다.

React.lazy

React.lazy 함수도 일반적인 컴포넌트를 동적으로 import할 수 있게 합니다.

// before
import OtherComponent from './OtherComponent';

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

React.lazy의 방식으로 import를 진행하게 된다면 MyComponent가 처음으로 렌더링이 되었을 때 OtherComponent를 포함한 번들을 자동으로 불러옵니다. 그리고 이 함수는 React 컴포넌트를 default export로 가진 모듈 객체가 이행되는 Promise를 반환해야 합니다.

import React, { Suspense } from 'react';

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

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

다음의 코드를 살펴보기로 하겠습니다. React.lazy에 있는 lazy 컴포넌트는 Suspense 컴포넌트의 하위에 렌더링되어야 합니다. 그리고 Suspense는 lazy 컴포넌트가 로드되길 기다리는 동안 로딩 화면과 같은 예비 컨텐츠를 보여줄 수 있게 해줍니다.

Suspense 컴포넌트에서 fallback이라고 하는 prop 내부에 또 다른 컴포넌트가 들어있는 모습을 볼 수 있습니다. 이 컴포넌트는 해당 컴포넌트가 로드될 때까지 기다리는 공안 렌더링하려는 React 엘리먼트를 받아들일 수 있습니다.

import React, { Suspense } from 'react';

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

또한 다음과 같이 여러 개의 lazy 컴포넌트들이 하나의 Suspense 컴포넌트로 감싸여질 수도 있습니다.

import React, { Suspense } from 'react';
import Tabs from './Tabs';
import Glimmer from './Glimmer';

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

function MyComponent() {
  const [tab, setTab] = React.useState('photos');
  
  function handleTabSelect(tab) {
    setTab(tab);
  };

  return (
    <div>
      <Tabs onTabSelect={handleTabSelect} />
      <Suspense fallback={<Glimmer />}>
        {tab === 'photos' ? <Photos /> : <Comments />}
      </Suspense>
    </div>
  );
}

이번에는 다음의 예시를 살펴보기로 하겠습니다. Suspense 컴포넌트에는 fallback props로 Glimmer 컴포넌트가 들어있습니다. 그리고 이 내부에는 Photos 컴포넌트와 Comments 컴포넌트가 들어 있습니다. 만약에 tab이 photos인 경우에는 Photos 컴포넌트가 보여질 것이고 그렇지 않은 경우에는 Comments가 보여질 것입니다.

이 상황에서, Photos에서 Comments 컴포넌트가 보여져야 하는데, Comments 컴포넌트 내부가 아직 준비되지 않은 상황이라면 어떻게 할까요? 이 경우에 fallback props로 Glimmer를 보여주게 됩니다.

네트워크 장애’를 보여주고자 하는 경우에는 다음과 같이 작성할 수도 있습니다.

import React, { Suspense } from 'react';
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>
);

다음 코드를 살펴보면, div 엘리먼트 내부에 MyErrorBoundary라고 하는 에러 경계가 감싸여져 있는 모습을 볼 수 있을 것입니다. 에러 경계에 대한 자세한 내용은 ‘https://ko.reactjs.org/docs/error-boundaries.html’ 이 웹사이트를 참고해 주세요.

이 내부에 Suspense 컴포넌트와 lazy 컴포넌트를 감싸는 경우에는 네트워크 장애가 발생했을 때 에러를 표시할 수 있습니다.

애플리케이션에 코드 분할을 적용하기 가장 좋은 곳은 바로 ‘라우트’를 진행하는 위치에 넣는 것입니다.

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

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

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

일반적으로 다음과 같이 코드를 짜면서 React.lazy를 라우트 기반으로 코드를 분할할 수 있습니다. 예시는 다음과 같습니다.

참고자료

profile
빛이 되고 싶고 별이 되고 싶은 개발자입니다 ;)

0개의 댓글