React.js - code spliting

Gyu·2022년 12월 5일
0

React.js

목록 보기
17/20
post-thumbnail

코드 스플리팅이란?

  • 리액트 프로젝트를 완성하여 사용자에게 제공할 때는 빌드 작업을 거쳐 배포해야하며, 이는 웹팩이 담당한다.
  • 웹팩에 별도의 설정을 하지 않으면 프로젝트에서 사용 중인 모든 자바스크립트 파일이 하나의 파일로 합쳐지고, 모든 CSS 파일도 하나의 파일로 합쳐진다.
  • CRA(create-react-app)로 프로젝트를 빌드할 경우 최소 두 개 이상의 자바스크립트 파일이 생성된다. CRA의 기본 웹팩 설정에는 SimpleChunk라는 기능이 적용되어 node_modules에서 불러온 파일, 일정 크기 이상의 파일, 여러 파일 간에 공유된 파일을 자동적으로 따로 분리시켜서 캐싱의 효과를 제대로 누릴 수 있게 해준다.
  • npm run build 명령어 실행 시 아래와 같이 build/static 디렉토리 내부에 자바스크립트 파일이 여러 개 생긴 것을 볼 수 있다.

  • 파일 이름에는 해시 값이 포함되어 있는데 이는 빌드하는 과정에서 해당 파일의 내용에 따라 생성되며, 이를 통해 브라우저가 새로운 파일을 받아야 할지 받지 말아야 할지를 알 수 있다.
  • 2로 시작하는 파일에는 React, ReactDOM 등 node_modules에서 불러온 라이브러리 관련 코드가 들어있고, main으로 시작하는 파일에는 직접 프로젝트에 작성하는 App 같은 컴포넌트에 대한 코드가 들어있다.
  • 웹팩의 SplitChunk라는 기능을 통해 자주 바뀌지 않는 코드들이 2로 시작하는 파일에 들어가있기 때문에 캐싱의 이점을 더 오래 누릴 수 있다.
  • 이렇게 파일을 분리하는 작업을 코드 스플리팅이라고 한다. 프로젝트에 기본 탑재된 SplitChunk 기능을 통한 코드 스플리팅은 단순히 효율적인 캐싱 효과만 있을 뿐이다.
  • 페이지 A, B, C로 구성된 SPA를 개발한다고 할 때, 사용자가 방문하지 않은 페이지에서 사용하는 컴포넌트 정보는 필요하지 않다. 하지만 리액트 프로젝트에서 별도로 설정하지 않으면 A, B, C 컴포넌트에 대한 코드가 모두 한 파일에(main) 저장되어 버린다. 만약 규모가 큰 애플리케이션에서 지금 당장 필요하지 않은 컴포넌트 정보도 모두 불러오면 파일 크기가 커지게 되고 그로 인해 로딩이 오래걸려 UX도 안 좋아지고 트래픽이 많이 발생하게 된다.
  • 이러한 문제점을 해결할 수 있는 방법 중 하나는 코드 스플리팅 방법 중 하나인 비동기 로딩이다. 코드 비동기 로딩을 통해 자바스크립트 함수, 객체 혹은 컴포넌트를 처음에 불러오지 않고 필요한 시점에 불러와 사용할 수 있다.

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

// notify.js - 비동기로 로딩할 파일
export default function notify() {
	alert('안녕하세요.');
}

// App.js
import React from 'react';
import notify from './notify';

const App = () => {
	const onClick = () => {
		notify();
	};

	return (
		<div>
      <h1>Hello React<h1>
			<p onClick={onClick}>click!</p>
    </div>
	)
}
  • 위와 같이 코드를 작성하고 빌드하면 notify 코드가 main 파일 안에 들어간다.
// App.js
import React from 'react';

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

	return (
		<div>
      <h1>Hello React<h1>
			<p onClick={onClick}>click!</p>
    </div>
	)
}
  • 위 코드와 같이 import를 상단에서 하지 않고 import() 함수 형태로 메서드 안에서 사용하면 파일을 따로 분리시켜 저장한다. 그리고 실제 함수가 필요한 지점에서 파일을 불러와 함수를 사용할 수 있다.
  • import 함수를 사용하면 Promise 를 반환한다. 이렇게 import 함수를 사용하는 문법은 표준 자바스크립트 방식은 아니지만, stage-3 단계에 있는 dynamic import라는 문법이다. 현재 웹팩에서 지원하고 있으므로, 별도의 설정 없이 프로젝트에서 바로 사용 가능하다. 이 함수를 통해 모듈을 불러올 때 모듈에서 default로 내보낸 것은 result.default를 참조해야 사용할 수 있다.

리액트에서 컴포넌트 코드 스필리팅

  • 리액트에 내장된 유틸 함수인 React.lazy와 컴포넌트인 Suspense는 리액트 16.6 버전부터 도입되었으며, 코드 스플리팅을 위해 사용된다.
  • 16.6 이전 버전에서는 import 함수를 통해 모듈을 불러온 다음, 컴포넌트 자체를 state에 넣는 방식으로 구현해야 한다.

State를 사용한 코드 스플리팅

  • 16.6 이전 버전에서 사용할 수 있는 코드 스플리팅 방식
  • 스플리팅할 컴포넌트
    import React from 'react';
    
    const SplitMe = () => {
      return <div>SplitMe</div>;
    };
    
    export default SplitMe;
  • App.js
    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('./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>
    		)
    	}
    }
  • state를 사용하여 컴포넌트 코드 스플리팅하는 방식은 어렵지 않지만 매번 state를 선언해야 한다는 점이 조금 불편하다.

React.lazy와 Suspense를 사용한 코드 스플리팅

  • React.lazy와 Suspense를 사용하면 코드 스플리팅을 하기 위해 state를 따로 선언하지 않고도 간편하게 컴포넌트 코드스플리팅을 할 수 있다.

React.lazy

  • 컴포넌트를 렌더링하는 시점에서 비동기적으로 로딩할 수 있게 해주는 유틸 함수
    const SplitMe = React.lazy(() => import('./SplitMe'));

Suspense

  • 리액트 내장 컴포넌트로서 코드 스플리팅된 컴포넌트를 로딩하도록 발동시킬 수 있고, 로딩이 끝나지 않았을 때 보여줄 UI를 설정 할 수 있다.
    import React, { useState, Suspense } from 'react';
    
    (...)
    <Suspense fallback={ <div>loading...</div> }
    	<SplitMe />
    </Suspense>
    
    // fallback props를 통해 로딩 중에 보여줄 JSX를 지정할 수 있다.

React.lazy와 Suspense를 사용한 예

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

const 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 React!
        </p>
        <Suspense fallback={ <div>loading...</div> }
					{ visible && <SplitMe /> }
				</Suspense>
      </header>
    </div>
  );
};

Loadable Component를 사용한 코드 스플리팅

  • Loadable Component는 코드 스플리팅을 편하게 하도록 도와주는 서드파티 라이브러리다. React.lazy와 Suspense는 서버사이드 렌더링을 지원하지 않는데 반해 이 라이브러리는 서버사이드 렌더링을 지원한다. 또한 렌더링 하기 전에 필요할 때 스플리팅된 파일을 미리 불러올 수 있는 기능도 있다.

  • 사용법은 React.lazy와 비슷하며, Suspense를 사용할 필요가 없다.

  • 설치 명령어 : npm install @loadable/component

    import React, { useState } from 'react';
    import logo from './logo.svg';
    import './App.css';
    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 React!
            </p>
            {visible && <SplitMe />}
          </header>
        </div>
      );
    }
    
    export default App;
  • 로딩중 다른 UI를 보여주고 싶을 때

    const SplitMe = loadable(() => import('./SplitMe'), {
      fallback: <div>loading...</div>
    });
  • 컴포넌트를 미리 불러오고 싶을 때

    import React, { useState } from 'react';
    import logo from './logo.svg';
    import './App.css';
    import loadable from '@loadable/component';
    const SplitMe = loadable(() => import('./SplitMe'), {
      fallback: <div>loading...</div>
    });
    
    function App() {
      const [visible, setVisible] = useState(false);
      const onClick = () => {
        setVisible(true);
      };
      const onMouseOver = () => { // 미리 불러오기
        SplitMe.preload();
      };
      return (
        <div className="App">
          <header className="App-header">
            <img src={logo} className="App-logo" alt="logo" />
            <p onClick={onClick} onMouseOver={onMouseOver}>
              Hello React!
            </p>
            {visible && <SplitMe />}
          </header>
        </div>
      );
    }
    
    export default App;
profile
애기 프론트 엔드 개발자

0개의 댓글