🐕 코드 스플리팅
리액트 프로젝트를 완성하여 사용자에게 제공할 때에는 빌드 작업을 거쳐 배포해야 합니다.
빌드 작업을 통해 자바스크립트 파일 안에서 불필요한 주석, 공백 등을 제거하여
파일크기를 최소화하기도 하고
브라우저에서 JSX 문법이나 최신 자바스크립트 문법이 원활하게 실행될 수 있도록
코드의 트랜스파일 작업도 같이 수행하곤 합니다.
만약 프로젝트내에 이미지와 같은 정적 파일이 있다면 해당 파일을 위한 경로도 설정하고요!
이러한 작업은 Create React-app에서는 웹팩이 담당합니다.
웹팩에서 별도의 설정을 해주지 않으면 프로젝트에서 사용 중인 모든 자바스크립트 팡리은
하나의 파일로 합쳐지고 모든 CSS 파일도 하나의 파일로 합쳐지게 됩니다.
CRA로 프로젝트를 빌드하는 경우 최소 두개 이상의 자바스크립트 파일이 생성되게 되는데
CRA의 기본 웹팩 설정에는 SplitChunks라는 기능이 적용되어
node_modules에서 불러온 파일, 일정 크기 이상의 파일, 여러 파일 간 공유된 파일
등을 자동으로 따로 분리시켜서 캐싱의 효과를 제대로 누릴수 있게 도와줍니다.
다만 splitChunks는 효율적인 캐싱 효과만 있을 뿐입니다!
그렇기 때문에 리액트 프로젝트에서 별도로 설정해주지 않으면
SPA를 개발하는 상황에서는 A페이지만 렌더링해도 되는 상황에서
모든 컴포넌트를 한파일에 저장한 탓에 모든 페이지를 불러오게 되고
이는 사용자 경험의 저해로 이어집니다.
이러한 문제를 해결해주기 위해서 코드 비동기 로딩이라는 방법을 채택할 수 있습니다.
코드 비동기 로딩을 통해 자바스크립트 함수, 객체, 컴포넌트를
필요한 시점에 불러와서 사용할 수 있습니다.
실습을 통해 코드스플리팅을 해보겠습니다.
👻다이나믹 임포트(Dynamic import)
src/notify.js
export default function notify() {
alert('안녕하세요');
}
간단한 모듈을 하나 만들어 주겠습니다.
실행시키면 alert 창이 뜨는 함수를 작성해서 내보내줬습니다.
/src/app.js
import logo from './logo.svg';
import './App.css';
import notify from './notify';
function App() {
const onClick = () => {
notify();
};
return (
<div className="App">
<p onClick={onClick}> hello react</p>
</div>
);
}
export default App;
App.js에서 우리가 작성한 notify를 불러와 사용해줬습니다.
이렇게 평범하게 코드를 작성하고 빌드하면 notify 코드는 main 파일 안에 들어가게됩니다.
하지만 위와 같이 import를 상단에서 하지 않고
다이나믹 임포트 문법을 사용해 메서드 안에서 사용하면 파일을 따로 분리시켜서 저장합니다.
다이나믹 임포트 문법은 다음과 같이 작성합니다.
import('경로')
굉장히 간단하네요
https://ko.javascript.info/modules-dynamic-imports
위 링크에서 자세한 정보를 확인할 수 있습니다.
얼핏보면 함수호출을 하는것 같아 보이지만
super()처럼 소괄호를 사용하는 자바스크립트의 문법이라고 합니다.
이러한 동적임포트 문법은 Promise를 반환합니다.
따라서 프로미스 후속처리 메서드 then, catch, finally를 사용하는 것 또한 가능합니다.
그렇다면 아까전의 코드를 이렇게 수정할 수 있을 것입니다.
import logo from './logo.svg';
import './App.css';
import notify from './notify';
function App() {
const onClick = () => {
import('./notify').then((result) => result.default());
};
return (
<div className="App">
<p onClick={onClick}> hello react</p>
</div>
);
}
export default App;
이렇게 동적 import를 이용해 모듈을 불러올 때 모듈에서 default 구문을 통해 내보낸 것은
성공 결과의 default를 참조해야 사용할 수 있습니다.
그런 다음 build 명령어를 이용해 build를 해보면
동적 임포트를 사용하기 전과 비교했을때 파일이 더 많이 생겨있는 것을 확인할 수 있습니다.
🥶 React.lazy
이러한 코드 스플리팅을 위해 리액트는 16.6버전부터 내장기능으로
유틸함수 React.lazy와 컴포넌트 Suspense를 제공합니다.
만약 리액트 16.6 이전 버전을 사용하고자 한다면 import 함수를 통해 불러온 다음
컴포넌트 자체를 state에 넣는 방식으로 구현해야 했다는데
이젠 유틸함수를 통해 쉽게 구현할 수 있어졌습니다.
React.lazy는 컴포넌트를 렌더링 하는 시점에서 비동기적으로 로딩할 수 있게 해주는 유틸함수 입니다.
const splitMe = React.lazy(()=> import('./SplitMe))
오 쉬운데요
아까 그 동적임포트를 트리거하는 함수를 콜백으로 React.lazy에 전달해주면 끝이네요
이렇게 만든 splitMe를 사용하고 싶을 때는 Suspense라는 리액트 내장 컴포넌트를 사용할 수 있습니다.
Suspense는 리액트 내장 컴포넌트로 코드 스플리팅된 컴포넌트를 로딩하도록 발동시킬 수 있고
로딩이 끝나지 않았을 때 보여줄 UI를 설정하는 것도 지원해줍니다!
Suspense의 사용방법도 contextAPI나 리덕스에 익숙하다면 굉장히 쉽게 받아들일 수 있습니다.
import { suspense } from 'react'
(...)
<Suspense fallback={<div> loading... </div>}>
<SplitMe />
</Suspense>
Suspense의 fallback 속성을 통해 로딩 중에 보여줄 jsx를 지정해줄 수 있군요!
import logo from './logo.svg';
import './App.css';
import notify from './notify';
import React, { useState, Suspense } from 'react';
const SplitMe = React.lazy(() => import('./SplitMe'));
function App() {
const [visible, setVisible] = useState(false);
const onClick = () => {
setVisible(true);
};
return (
<div className="App">
<p onClick={onClick}> hello react</p>
<Suspense fallback={<div>loading...</div>}>
{visible && <SplitMe />}
</Suspense>
</div>
);
}
export default App;
위와 같이 App.js를 구성해줍니다.
visible의 상태가 true일때만 SplitMe를 렌더링하는 코드입니다.
SplitMe는 리액트레이지를 통해 임포트해오고요!
SplitMe는 간단한 컴포넌트로 구성해주면 됩니다.
전 간단하게 이런식으로 구성했어요
const SplitMe = () => {
return <div>SplitMe</div>;
};
export default SplitMe;
너무 간단하다..
그러고 SplitMe가 렌더링 되어야 하는 상황을 설정해주면
스플리팅되어있던 SplitMe가 불러와지는것을 볼 수 있습니다.
개쩐다
이렇게 Network탭에서 전 Slow 3G라고 설정한 부분이 보이시나요?
저 부분을 통해 네트워크 속도를 조정할 수 있습니다.
로딩화면이 잘 표시되는지 확인하기 위해 네트워크속도를 느리게 바꿔준 뒤
확인을 해보고 다시 원래 설정으로 바꿔주겠습니다.
🐶마치며
간단하게 React.lazy와 Suspense를 활용한 실습을 해봤습니다.
다음엔 Loadable Components라는 라이브러리를 통해 실습을 해보겠습니다.
'react' 카테고리의 다른 글
useRef와 forwardRef 사용법을 동시에 배울 수 있는 글이 있다? (2) | 2023.05.22 |
---|---|
React.createPortal을 이용해 모달 만들기 (0) | 2023.05.15 |
immer를 이용해 깊은 복사를 케이크처럼 간단히 먹자 (1) | 2023.04.08 |
React Router를 이용해보자 근데 실습을 곁들인 (0) | 2023.04.07 |
react-virtualized 를 사용한 렌더링 최적화를 해보자 (0) | 2023.04.06 |