Jun 개발노트

Code Split

2020-08-31

왜? 해야하나요?

CRA는 웹팩을 통해 여러개로 나누어진 파일(js, css)를 하나의 파일로 만든다(번들링)

  • 이유

    • 여러번 요청하는게 아니라 한번에 파일을 받게 한다

    • 데이터기반으로 빠르게 UI를 변경시켜 사용자에게 좋은 UX를 제공한다(서버 요청 없이 페이지 변경)

  • 문제점

    • 번들링 된 파일이 거대해지면, 첫페이지를 로딩시간이 길어진다.

    • 번들링 된 파일에는 사용자가 불필요한 코드가 포함되어 있다.

방법

1. import()

  • CRA는 기본적으로 Webpack에 codesplit이 설정이 되어있다.
  • Webpack이 번들링할때, import()문을 만나면 나누어서 번들링을 한다.
// 예제 1)
// 하나의 파일로 번들링이 된다.
import { add } from './math';

console.log(add(16, 26));

// dynamic import를 통해 파일이 분리가 된다.
import("./math").then(math => {
  console.log(math.add(16, 26));
});

// src/func.js

export function func() {
    console.log('hello world!')
}

// 예제 2)
// src/App.js
import React, { Component } from 'react';

class App extends Component {
    handleClick = () => {
        import('./func').then(result => {
            result.func();
            // 물론 export defualt로 내보내주면 result.default()가 됩니다.
        });
    };
    render() {
        return (
            <div className="App">
                <button onClick={this.handleClick}>불러오기</button>
            </div>
        );
    }
}

export default App;

//예제 3) 
// 함수와 달리 컴포넌트는 바로 호출하는 것이 아니고
// 있을 때 호출해야 하는 조건을 달아줘야 하므로 state로 관리
import React, { Component } from 'react';

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

    handleClick = () => {
        import('./Test').then(result => {
            this.setState({
                Test: result.default
            });
        });
    };

    render() {
        const { Test } = this.state;

        return (
            <div className="App">
                <button onClick={this.handleClick}>불러오기</button>
                {Test && <Test />}
            </div>
        );
    }
}

export default App;
  • 또한 바벨 이용시 import를 통해서 코드스플릿이 되게 플러그인을 설치해준다.
// $ npm install babel-plugin-syntax-dynamic-import
// .babelrc
{
  "plugins": ["syntax-dynamic-import"]
}

2. React.lazy, Suspense

  • React.lazy / Suspense는 아직 서버 사이드 렌더링을 미지원(공식 문서)

  • React.lazy는 default export를 가진 모듈을 반환한다.

  • Suspense 컴포넌트의 children으로 React.lazy이다

    • fallback을 통해 해당 컴포넌트 로딩화면을 구현 할 수 있다.

    • 하나의 Suspense 컴포넌트 하위에 여러개의 React.lazy 컴포넌트를 구성할 수 있다.

import React, { Suspense } from 'react';

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

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

3. Loadable Components

  • React.lazy보다 지원범위가 넘음

Loadable 지원범위

  • 사용법은 lazy와 동일하며, 두번째 매개변수로 fallback 구현 or fallback Props로 구현가능
import React, { Suspense } from 'react';
import loadable from '@loadable/component'

const OtherComponent = loadable(() => import('./OtherComponent'), {
	fallback : <div>Loading...</div>
})

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

4. 만약 Loading 중에 깜박거리는 느낌을 받는다면 ?

  • 화면 전환을 지연시킨다(500ms의 지연을 겪게하여 오히려 편한 UX를 제공할 수 있다.)
// npm inatall p-min-delay

import React, { Component } from 'react';
import loadable from '@loadable/component';
import pMinDelay from 'p-min-delay';

const Test = loadable(() => pMinDelay(import('./Test'), 500), {
    fallback: <div>Loading...</div>
});

class App extends Component {
    state = {
        visible: false
    };

    handleClick = () => {
        this.setState({
            visible: true
        });
    };

    render() {
        const { visible } = this.state;
        return (
            <div>
                <button onClick={this.handleClick}>댓글보기</button>
                {visible && <Test />}
            </div>
        );
    }
}

export default App;

언제 사용할까?

Router 기반으로 CodeSplit 구현

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

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

주의사항

  1. React.lazy 같은 경우 default exports만 지원(공식문서)
  2. names exports를 사용할 경우 default로 이름을 재정의해야 한다.
// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;

// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";