티스토리 뷰

WEB/React

[React] Code splitting

춘햄 2023. 4. 6. 18:04

프로젝트를 배포하기 위해서는 반드시 빌드를 해야한다. 

 

이 빌드 작업은 Webpack이라는 도구가 처리하는데, 웹팩에서 별도의 설정을 하지 않으면 프로젝트에서 사용 중인 모든 자바스크립트 파일을 하나의 파일로 합쳐지고, 모든 CSS 파일도 하나의 파일로 합쳐진다.

 

리액트에서는 이 빌드 과정에서 화면에 보여지지 않거나 당장 사용하지 않는 기능이 들어간 파일들을 미리 빌드하지 않고, 필요한 시점에 동적으로 불러와서 사용할 수 있게끔 해주는 기능을 구현할 수 있다. 

 

이를 코드 스플리팅이라고 하며, 바로 한번 알아보도록 하자.


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

컴포넌트 코드를 스플리팅하기에 앞서 일반 자바스크립트 함수를 스플리팅 해보면서 어떤 개념인지 익혀보자.

 

우선, 아래와 같은 코드가 있을 때

 

◎notify.js

export default function notify() {
    alert('안녕하세요!');
}

 

◎App.js

import logo from './logo.svg';
import './App.css';
import notify from './notify';

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

export default App;

이 코드는 App 컴포넌트에서 p 태그를 클릭하기도 전에 notify 라는 js 파일을 미리 임포트하여 가지고 있다가 nofity에 있는 함수를 호출하여 사용한다.

 

굳이 미리 가지고 있지 않고, 클릭 이벤트를 핸들링하는 시점에 notify 파일을 임포트하게 하는 것이 코드 스플리팅의 개념이다.

 

import를 함수로 사용하여 Promise를 반환받아 dynamic Import 를 사용할 수 있다.

 

◎App.js

import logo from './logo.svg';
import './App.css';

function App() {
  const onClick = () => {
    import('./notify').then(result => result.default());
  }
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p onClick={onClick}>Hello!</p>
      </header>
    </div>
  );
}

export default App;

 

 

위 영상처럼 이벤트가 핸들링되는 시점에 static/js 디렉토리에 해당 js 파일이 추가되는 걸 확인할 수 있다.


React.lazy와 Suspense를 통한 컴포넌트 코드 스플리팅

코드 스플리팅을 위해 리액트에 내장된 기능으로 유틸 함수인 React.lazy와 컴포넌트인 Suspense가 있다.

 

React.lazy

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

 

사용 방법은 아래와 같다.

 

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

 

Suspense

Suspense는 리액트 내장 컴포넌트로서 코드 스플리팅된 컴포넌트를 로딩하도록 발동시킬 수 있고, 로딩이 끝나지 않았을 때 보여 줄 UI를 설정할 수 있다.

 

사용 방법은 아래와 같다.

import {Suspense} from 'react';

(...)

<Suspense fallback={<div>loading...</div>}>
	<SplitMe />
</Suspense>

Suspense 에서 fallback props를 통해 로딩 중에 보여 줄 JSX를 지정할 수 있다.


이제 코드에 적용해보자.

 

◎SplitMe.js

const SplitMe = () => {
    return <div>SplitMe</div>;
};

export default SplitMe;

위와 같은 div 태그를 가진 컴포넌트가 하나 있다고 할 때,

 

◎App.js

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

function App() {
    const SplitMe = React.lazy(() => import('./SplitMe'));
    const [visible, setVisible] = useState(false);
    const onClick = () => {
        setVisible(true);
    };
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p onClick={onClick}>Hello!</p>
          <Suspense fallback={<div>loading...</div>}>
              {visible && <SplitMe/>}
          </Suspense>
      </header>
    </div>
  );
}

export default App;

위와 같이 간단하게 SplitMe 컴포넌트를 동적으로 임포트하여 사용할 수 있다.

 


Loadable Components

Loadable Components는 코드 스플리팅을 편하게 하도록 도와주는 서드파티 라이브러리이다. 

 

이 라이브러리는 React.lazy와 Suspense는 지원하지 않는 서버 사이드 렌더링을 지원하며 렌더링 하기 전에 스플리팅된 파일을 미리 불러올 수 있다는 장점이 있다.

 

우선 서버 사이드 렌더링은 아직 다루지 않았으니 서버 사이드 렌더링 없이 Loadable Components를 다루는 법을 알아보도록 하자.

 

일단 패키지부터 설치하자.

npm install @loadable/component

 

사용법은 React.lazy와 꽤 비슷하지만, Suspense를 사용할 필요는 없다.

 

◎App.js

import logo from './logo.svg';
import './App.css';
import {useState, Suspense} from 'react';
import loadable from '@loadable/component';
const SplitMe = loadable(() => import('./SplitMe'));

function App() {
    const [visible, setVisible] = useState(false);
    const onClick = () => {
        setVisible(true);
    };
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p onClick={onClick}>Hello!</p>
          {visible && <SplitMe/>}
      </header>
    </div>
  );
}

export default App;

 

만약 로딩 중에 다른 UI를 보여 주고 싶다면 loadable을 사용하는 부분을 아래와 같이 수정하면 된다.

const SplitMe = loadable(() => import('./SplitMe'), {
    fallback: <div>loading....</div>
});

 

또한 preload 함수를 사용하여 컴포넌트를 미리 불러올 수도 있다.

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

function App() {
    const [visible, setVisible] = useState(false);
    
    const onMouseOver = () => {
        SplitMe.preload();
    };
    
    const onClick = () => {
        setVisible(true);
    };
    
  return (
    <div className="App">
      <header className="App-header">
        <img src={logo} className="App-logo" alt="logo" />
        <p onClick={onClick} onMouseOver={onMouseOver}>Hello!</p>
          {visible && <SplitMe/>}
      </header>
    </div>
  );
}

export default App;

preload를 사용하면 Hello에 마우스를 올려두기만 해도 미리 해당 컴포넌트를 로드하여 가지고 있게 된다. 

 

Loadable Components는 미리 불러오는 기능 말고도 타임아웃, 로딩UI딜레이, 서버 사이드 렌더링 호환 등 다양한 기능을 제공하니 여기에서 확인하자.

 

끝!

Comments