01.18 리액트 코드 스플리팅
React.js |
01.18. 리액트 코드 스플리팅
싱글 페이지 애플리케이션의 단점은 페이지 로딩 속도가 지연될 수 있다는 것이다. 로딩 속도가 지연되는 이유는 자바스크립트 번들 파일에 모든 애플리케이션의 로직을 불러오므로 규모가 커지면서 용량도 커지기 때문이다. 하지만 이 문제는 코드 스플리팅(code splitting)을 하면 해결할 수 있다.
코드 스플리팅(code splitting)은 말 그대로 코드를 분할한다는 의미이다. webpack에서 프로젝트를 번들링할 때 파일 하나라 아니라 여러 개로 분리시켜서 결과물을 만들 수 있다. 또 페이지를 로딩할 때 한꺼번에 불러오는 것이 아니라 필요한 시점에 불러올 수도 있다.
01.18.1. 코드 스플리팅의 기본
webpack4 이전의 버전에서는 vendor 를 직접 설정해야 했지만, webpack4 부터는 자동으로 생성해주기 때문에 별도의 설정은 하지 않아도 된다.
01.18.1.1. 비동기적 코드 불러오기: 청크생성
페이지에서 필요한 코드들만 불러오려면, 청크(chunk)를 생성해야한다. 청크를 생성하면 페이지를 로딩할 때 필요한 파일만 불러올 수 있고, 아직 불러오지 않은 청크 파일들은 나중에 필요할 때 비동기적으로 불러와 사용하라 수 있다.
// src/components/SplitMe.js import React from 'react'; const SplitMe = () => { return ( <h3>청크</h3> ); }; export default SplitMe;
청크를 생성할 컴포넌트 자체는 특별히 하는 것이 없다. 다만 이 컴포넌트를 불러오는 것이 평상시와는 조금 다르다. 비동기적으로 파일을 불러오려면 import 를 코드 최상단에 적는 것이 아니라, 특정 함수 내부에서 작성한다. LifeCycle 메서드 안에 넣을 수도 있고, 별도의 이벤트를 설정하여 불러오도록 설정할 수도 있다.
SplitMe 를 비동기적으로 불러올 AsyncSplitMe 컴포넌트를 만들어 버튼을 눌렀을 때, SplitMe 컴포넌트를 불러와 state 에 담고 이를 렌더링한다.
// src/components/AsyncSplitMe.js import React, {Component} from 'react'; class AsyncSplitMe extends Component { state = { SplitMe: null } loadSplitMe = () => { // 비동기적으로 코드를 불러온다. 함수는 Promise 를 결과로 반환한다. // import() 는 모듈의 전체 네임스페이스를 불러오므로, default 를 직접 지정해야한다. import('./SplitMe').then(({ default: SplitMe }) => { this.setState({ SplitMe }); }); } render() { const { SplitMe } = this.state; // SplitMe 가 있으면 이를 렌더링하고, 없으면 버튼을 렌더링한다. // 버튼을 누르면 SplitMe 를 불러온다. return SplitMe ? <SplitMe /> : <button onClick={this.loadSplitMe}>SpdlitMe 로딩</button> } } export default AsyncSplitMe;
// src/App.js import React from 'react'; import {Route} from 'react-router-dom'; import {Home, About, Posts} from 'pages'; import Menu from 'components/Menu'; import AsyncSplitMe from 'components/AsyncSplitMe'; const App = () => { return ( <div> <Menu /> <AsyncSplitMe /> (...) </div> ); } export default App;
크롬 개발자 도구의 Network 탭을 열어 SplitMe 비동기적 로딩 버튼을 누르면 네트워크에는 2.chunk.js 파일을 불러온 기록이 남는다.
이렇게 코드 위에서 import __ from __ 이 아닌 import() 함수로 컴포넌트를 불러오면 webpack 은 청크를 생성하여 저장한다.
이렇게 코드 위에서 import __ from __ 이 아닌 import() 함수로 컴포넌트를 불러오면 webpack 은 청크를 생성하여 저장한다.
01.18.1.2. 라우트에 코드 스플리팅
SplitMe, AsyncSplitMe 는 더 이상 사용하지 않기 때문에 삭제하고, App 에서 import 했던 코드도 삭제한다.
- asyncComponent 함수 생성
비동기적으로 불러올 코드가 많으면 청크를 생성할 때마다 파일에 비슷한 코드들을 반복하여 작성해야한
다. 조금 더 편하게 구현할 수 있도록 따로 함수화하여 재사용한다.
다. 조금 더 편하게 구현할 수 있도록 따로 함수화하여 재사용한다.
// lib/asyncComponent.js import React from 'react'; export default function asyncComponent (getComponent) { return class AsyncComponent extends React.Component { static Component = null; state = { Component: AsyncComponent.Component }; constructor(props) { super(props); if (AsyncComponent.Component) return ; getComponent().then(({default: Component}) => { AsyncComponent.Component = Component; this.setState({Component}); }); } render() { const { Component } = this.state if (Component) { return <Component {...this.props} /> } return null; }; } };
이 함수는 컴포넌트를 import 하는 함수를 호출하는 함수를 파라미터로 받는다.
asyncComponent( () => import('./Home') );
그리고 파라미터로 받은 함수는 constructor 에서 실행하여 컴포넌트를 불러온다. 해당 컴포넌트를 실제로 렌더링할 때 파일을 불러오도록 설정한 것이다. 컴포넌트가 로딩되면 불러온 컴포넌트를 state 에 집어넣고, 또 static 값으로도 설정한다.
컴포넌트가 언마운트되었다가 나중에 다시 마운트될 때는 컴포넌트를 다시 새로 불러오지 않고, static 값으로 남아있는 이전에 불러온 컴포넌트 정보를 재사용한다.
컴포넌트가 언마운트되었다가 나중에 다시 마운트될 때는 컴포넌트를 다시 새로 불러오지 않고, static 값으로 남아있는 이전에 불러온 컴포넌트 정보를 재사용한다.
01.18.1.3. 라우트 코드 스필리팅용 인덱스 생성
// src/pages/index.async.js import asyncComponent from '../lib/asyncComponent'; export const Home = asyncComponent( () => import('./Home') ); export const About = asyncComponent( () => import('./About') ); export const Post = asyncComponent( () => import('./Post') ); export const Posts = asyncComponent( () => import('./Posts') );
// src/App.js import React from 'react'; import {Route} from 'react-router-dom'; import {Home, About, Posts} from 'pages/index.async.js'; import Menu from 'components/Menu'; const App = () => { return ( <div> <Menu /> <Route exact path="/" component={Home} /> <Route path="/about/:name?" component={About} /> <Route path="/posts" component={Posts} /> </div> ); }; export default App;
크롬 개발자 도구의 Network 탭을 연 상태에서 메뉴의 링크를 클릭해서 이동해보면 비동기 로딩이 처릭 될 것이다.( Posts 컴포넌트는 index.async.js 파일로 치환하지 않았기 때문에 현재는 비동기 로딩이 되지 않는다. 나중에 설정할 것이기 때문에 무시해도 된다.)
코드 스플리팅은 프로젝트 규모가 클수록 효과가 있다. 소규모 프로젝트라면 코드 스플리팅을 해 보았자 3kb 미만으로 큰 효과는 없다.
소규모 프로젝트라면 코드 스플리팅은 생략해도 된다. 나중에 파일 크기가 좀 커졌다고 느낄 때 코드 스플리팅을 구현해도 무방하다.
댓글
댓글 쓰기