본문으로 바로가기

19. 코드 스플리팅

category React/리액트를 다루는 기술 2021. 2. 22. 21:27

프로젝트를 빌드할 때 별도의 설정을 하지 않는다면 프로젝트 내에 작성한 모든 파일들이 하나의 파일에 합쳐지게된다

그렇게 하나로 합쳐진 파일이 유저에게 제공되며 프로젝트의 크기가 커질수록 파일을 받아오는데 시간이 오래걸리며 당연하게 사용자 경험적 측면에서 좋지 못하다

이러한 문제를 해결해 줄 수 있는 방법이 코드 스플리팅인데 하나에 파일에 다 합쳐놓는 것이 아니라 어느정도 분할한 뒤 필요한 코드가 있는 파일들만 필요할 때 불러오는 것이다

https://s3-us-west-2.amazonaws.com/secure.notion-static.com/fcfbef00-39f8-49ec-8185-3fcff047ade8/Untitled.png

cra로 작성한 프로젝트를 빌드했을 때의 파일 구조

  • main으로 시작하는 파일들은 하나의 큰 덩어리로 우리가 자주 사용하는 코드들을 합쳐놓은 곳이다
    우리가 코드를 수정하고 다시 빌드하면 보통은 main에 합쳐진다
  • 2나 3으로 시작하는 파일들은 코드들이 잘 수정되지 않는 파일들로 캐싱의 이점을 오래 누릴 수 있는 코드들의 집합이다

19-1. 자바스크립트의 함수 비동기 로딩


cra로 프로젝트를 생성 한 뒤에 src 디렉토리에 notify.js 파일을 만들자

export default function (notify){
    alert('hello');
};

위의 함수를 App 컴포넌트에서 불러와서 사용할텐데 평소와 같이 사용한다면


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

function App() {
    function handleClick() {
        notify();
    }

    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;

이처럼 상단에 import한 후 빌드하게되면 위에서 언급한 main의 파일에 들어가게되지만

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

이런식으로 import를 함수로 사용하게 되면 main에 들어가서 미리 받아놓는 것이 아니라 handleClick이 호출되었을 때 새로운 자바스크립트 파일을 불러온다

import함수의 사용법


import를 함수로 사용하면 Promise를 반환하고 이렇게 불러올 때 모듈에서 default로 보낸 것은 result.default를 참조해야 사용할 수 있다

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


19-2-1. state를 사용한 코드 스플리팅


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

class App extends Component {
    state = {
        SplitMe: null,
    };
    handleClick = async ()=>{
        const loadedModule = await import('./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;

클래스형 컴포넌트로 만든 뒤 state를 이용하여 불러올 수 있다

19-2-2. React.lazy와 Suspense 사용하기


React.lazy와 Suspense를 사용하면 위에서 처럼 state를 사용할 필요 없이 간편하게 컴포넌트 코드 스플리팅을 할 수 있다

lazy는 컴포넌트를 렌더링하는 시점에서 비동기적으로 로딩할 수 있게 해 주는 유틸 함수이며 lazy 컴포넌트는 Suspense컴포넌트 하위에서만 사용되어야한다

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

Suspense는 리액트의 내장 컴포넌트로서 스플리팅된 컴포넌트를 로딩할 수 있으며 lazy컴포넌트를 불러오는 동안 보여줄 UI를 fallback props로 설정할 수 있다

import React, {Suspense} from 'react';

(...)

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

19-3. Loadable Components


Loadable Components는 코드 스플리팅을 도와주는 서드파티 라이브러리로 서버 사이드 렌더링을 지원한다

19-3-1. 사용법


사용법은 React.lazy와 매우 유사하다 lazy 대신 loadable로 import함수를 감싸주면된다

const SplitMe = loadable(()=>import('./SplitMe'));
...
<p onClick={()=>handleClick()}>Hello React</p>
          {visible && <SplitMe/>}
...

fallback


lazy와는 다르게 Suspense 컴포넌트를 사용하지 않기 때문에 fallback을 다르게 설정해야한다

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

preload


컴포넌트를 미리 불러오는 방법도 존재하는데 처음 서버와 연결할 때 불러오는 게 아니라

사용자 입력이 예상되는 시점에 컴포넌트를 미리 불러와서 사용자 경험에 기여하는 기술이다

...
        function handleMouseOver() {
        SplitMe.preload();
    }

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

이처럼 Hello React에 마우스를 올렸을 때 클릭이 예상되기 때문에 그 시점부터 SplitMe를 불러오게 할 수 있다

사용자가 실제로 클릭하게 된다면 불러오는 체감 시간이 감소하는 느낌을 받을 수 있다