React를 다루는 기술 19 - code splitting

3 minute read

code splitting이란 코드 상에 기본적으로 쓰이지 않는 함수, 라이브러리 등을 따로 떼어내어
호출되었을 때 비로소 불러와서 사용하도록 하는 방식입니다.
일반적인 방식으로 import를 하여 빌드를 진행할 경우 import 된 함수, 라이브러리가 사용되는지 유무는 묻지도 따지지도 않고 모두 하나의 파일로 합치게 되어 불필요하게 크기를 키우게 됩니다.

자바스크립트 함수 비동기 로딩

이러한 문제를 해결하기 위해 함수를 불러오는 import를 컴포넌트 내부에서 함수처럼 활용하여 splitting 할 수 있습니다.

// notify.js
export default function notify() {
  alert("안녕하세요");
}

// App.js
import React, { useState, Suspense } from 'react';
import logo from './logo.svg';
import './App.css';

function App() {
  const handleClick = () => {
    import('./notify').then(result => result.defaults);
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p onClick={handleClick}>Hello! React!</p>
      </header>
    </div>
  );
}

export default App;

위 코드를 실행하면 App 컴포넌트가 실행되었을때 notify.js를 불러오지 않게 됩니다.
대신 클릭하여 handleClick 함수를 호출하면 그때 import함수를 통해 notify.js 파일을 불러오게 되죠.
때문에 해당 프로젝트를 빌드하고 브라우저를 통해 확인해보면
프로젝트의 컴포넌트 부분을 담당하는 main으로 시작하는 js 파일 외에 위 코드에서 splitting을 통해 따로 떼어놓은 notify.js를 담은 js파일이 별도로 존재하는 것을 볼 수 있습니다.

state를 이용한 splitting

splitting을 지원하는 기능으로는 React.lazy와 Suspense가 있습니다만 이 둘은 16.6버전부터 추가된 기능들입니다.
그 전에는 불러온 함수를 State에 저장하여 사용했는데요. 우선 State를 이용한 방법을 확인해보죠.

import React, { Component } from 'react';
import logo from './logo.svg';
import './App.css';

class App extends Component {
  state = {
    SplitMe: null
  };

  handleClick = async () => {
    const loadedModule = await import('./components/SplitMe');
    this.setState({
      SplitMe: loadedModule.default
    });
  }

  render() {
    const { SplitMe } = this.state;
    return (
      <div className="App">
        <header className="App-header">
          <img src={logo} className="App-logo" alt="logo" />
          <p onClick={this.handleClick}>Hello! React!</p>
          {SplitMe && <SplitMe />}
        </header>
      </div>
    );
  }
}

export default App;

참고로 이 방식은 함수형 컴포넌트에서는 되지 않습니다.
공식적으로 16.8버전 이전에는 함수형 컴포넌트에서 State를 지원하지 않았기 때문인데요.
지원여부는 둘째치고서라도 매번 불러온 함수를 위해 State를 따로 만들어주어야한다는 점은 불편한 것이 사실입니다.

React.lazy와 Suspense

React.lazy는 컴포넌트를 렌더링 하는 시점에서 비동기적으로 함수를 로딩할 수 있게 해주는 유틸성 함수입니다.

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

Suspense는 리액트 내장 컴포넌트로써 splitting된 컴포넌트를 로딩하도록 하고 로딩 중에 보여줄 UI를 지정할 수 있습니다.

<Suspense fallback={<div>...Loading</div>}>
  {visible && <SplitMe />}
</Suspense>

여기 fallback prop을 통해 로딩 중에 보여줄 UI를 지정할 수 있습니다.
이 둘을 이용해 위에서 클래스형 컴포넌트에서 State를 이용해 splitting했던 코드를 수정하면 이하와 같습니다.

import React, { useState, Suspense } from 'react';
import logo from './logo.svg';
import './App.css';
const SplitMe = React.lazy(() => import('./components/SplitMe'));

function App() {
  const [ visible, setVisible ] = useState(null);
  const handleClick = async () => {
    setVisible(true);
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p onClick={() => { handleClick(); }}>Hello! React!</p>
        <Suspense fallback={<div>...Loading</div>}>
          {visible && <SplitMe />}
        </Suspense>
      </header>
    </div>
  );
}

export default App;

Loadable Components

Loadable Components는 splitting을 지원하는 서드파티 라이브러리입니다.
React.lazy와 Suspense와의 차별점으로는

  • React.lazy와 Suspense의 기능을 모두 담고 있다.
  • 서버 사이드 렌더링이 가능하다.
  • 렌더링하기 전에 필요할 때 splitting된 파일을 미리 불러올 수 있다.

가 있습니다.

import React, { useState } from 'react';
import logo from './logo.svg';
import './App.css';
import loadable from '@loadable/component';
const SplitMe = loadable(() => import('./components/SplitMe'), {
  fallback: <div>Loading...</div>
});

function App() {
  const [ visible, setVisible ] = useState(null);
  const handleClick = () => {
    setVisible(true);
  }

  const onMouseOver = () => {
    SplitMe.preload();
  }

  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p onClick={handleClick} onMouseOver={onMouseOver}>Hello! React!</p>
        {visible && <SplitMe />}
      </header>
    </div>
  );
}

export default App;

패키지 관리자를 통해 @loadable/component를 설치하고 React.lazy와 유사한 방식으로 원하는 컴포넌트를 불러옵니다.
여기에 파라미터로 fallback으로 JSX코드를 담으면 Suspense의 fallback과 동일하게 로딩 중에 UI를 표시할 수 있습니다.

위 코드에서 React.lazy, Suspense 코드와의 차이점이라면 preload 함수가 있는데요.
이는 loadable함수를 통해 선언해둔 컴포넌트를 렌더링하기 전에 미리 불러오는 역할을 합니다.
그리하여 Hello! React! 글자에 마우스를 올리는 순간 SplitMe 컴포넌트가 빌드된 js 파일을 불러오고 클릭하면 해당 js 파일로부터 SplitMe 컴포넌트를 렌더링하게 됩니다.

이처럼 splitting을 한다는 목적에 놓고 보면 React.lazy, Suspense와 loadable-components에 그다지 차이가 없지만
서버사이드 렌더링이 가능하다는 점 때문에 리액트 공식 문서에서는 가급적 loadable-components의 사용을 권장하고 있습니다.
서버사이드 렌더링에 대해서는 다음 장에서 알아보도록 하겠습니다.

출처자료

https://www.aladin.co.kr/shop/wproduct.aspx?ItemId=209354690 (리액트를 다루는 기술(개정판))

Categories:

Updated: