리액트 어플리케이션의 경우 빌드를 통해서 배포한다. 이 과정에서 파일 크기를 가능하면 최소화하는 것이 바람직하다.
왜냐하면 파일 크기가 성능을 결정하고 결과적으로 사용자 경험에까지 영향을 미치기 때문이다.
또한 브라우저에서 JSX
나 최신 자바스크립트 문법 등이 문제없이 잘 실행될 수 있도록 트랜스파일링하는 작업도 해주어야 한다.
💡 일반적으로 이러한 작업은
웹팩(webpack)
에서 담당한다.
따로 웹팩을 설정해주지 않으면 프로젝트의 모든 자바스크립트 파일은 하나의 파일로 합쳐지고, CSS 역시 하나의 파일로 합쳐지게 된다.하나의 파일로 모든 자바스크립트를 묶어서 빌드하면 파일의 크기가 매우 크고, 한 줄의 자바스크립트 코드만 수정해도 다시 모든 자바스크립트 코드들을 새로 빌드해야 하기 때문에 비효율성을 가지게 된다.
❗더 나은 사용자 경험을 위해 코드를 비동기적으로 로딩하는 방법
예를 들어 페이지가 /main
, /about
, /post
이렇게 세 가지 페이지로 이루어진 SPA
를 개발한다고 할 때
/main
으로 들어가는 동안 /about
이나 /post
페이지 정보는 사용자에게 필요하지 않을 확률이 높다.
💡 그러한 파일들을 분리하여 지금 사용자에게 필요한 파일만 불러올 수가 있다면 로딩도 빠르게 이루어지고 트래픽도 줄어 사용자 경험이 좋아질 수가 있다.
지금 당장 필요한 코드가 아니면 따로 분리시켜서, 나중에 필요할 때 불러와서 사용할 수 있다.
React.lazy
💡 컴포넌트를 렌더링하는 시점에 비동기적으로 로딩할 수 있게 해주는 유틸 함수이다.
Suspense
💡 리액트 내장 컴포넌트로 코드 스플리팅 된 컴포넌트를 로딩하고, 로딩이 끝나지 않았을 때 보여줄 UI를 설정할 수 있다.
fallback
이라는props
를 통해 로딩 중에 보여줄JSX
문법을 지정할 수 있다.
React.lazy
+ Suspense
import React, { Suspense } from 'react';
const SomeComponent = React.lazy(() => import('./SomeComponent'));
const myComponent = {
return (
<Suspense fallback={<div>Loading...</div>}>
<SomeComponent />
</Suspense>
)
}
React.lazy
를 쓰지 않고 스플리팅을 해야 한다면? 🙄16.6버전 이전에 했던 방식으로 분리할 컴포넌트를 state
에 선언하여 해당 모듈을 불러와야할 때 state
를 바꾸어 주는 식으로 진행한다.
그 중 가장 많이 쓰이는 방법 중 하나는 라우트 기반 분할이다.
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>
);
💡 라우트마다 다른 컴포넌트로 관리를 하고 있을 경우, 각 라우트를
import
함수를 통해 분리된 빌드 파일로 관리 할 수 있다.유저가 다른 페이지로 넘어갈때만 그 페이지를 비동기적으로 로딩할 수 있다.
💡 페이지 안에 있지만 보이지 않는 컴포넌트가 존재할 수 있다.
예를 들어 유저가 이메일 페이지에서 새로운 메일을 작성하고자 할 때, 작성하기 버튼을 눌러 모달이 뜨게 된다면 그 모달을
import()
로 스플리팅해서 관리할 수 있다.
모달 대신 alert
함수 스플리팅하기
Notify.tsx
const Notify = () => {
window.alert('notify!');
};
export default Notify;
App.tsx
import React, {useState} from 'react';
import {TextField, Box, Button} from '@mui/material';
const App = () => {
const handleNotify = () => {
import('./Notify').then(({default: Notify}) => {
Notify();
});
};
return (
<Button variant='contained' onClick={handleNotify}>
추가
</Button>
)
}
export default App
💡
Button
을 눌렀을 때example.chunk.js
라는 파일을 불러오게 된다.
import
함수를 사용하면 웹팩이 알아서 코드를 분리해서 저장해주고,import
가 호출할 때 불러와서 사용할 수 있게 해준다.
❗여기서는 함수 level을 스플리팅했다.
**chuck
파일 생성 차이**
스플리팅 전
스플리팅 후
💡 페이지 하나가 되게 긴 경우,
그 페이지에 들어갈 때 당장 보이는 부분을 나머지와 분리하고 그 뒷부분을 다른 컴포넌트로 만들어 스플리팅할 수 있다.
Entry Point
는 웹팩이 앱에서 번들링하려는 모듈의 진입파일이다.
리액트 앱이 여러 엔트리 포인트를 설정한다면 각각의 엔트리 포인트 마다 코드 스플리팅이 가능하다.
// webpack.config.js
const path = require('path');
module.exports = {
mode: 'development',
entry: {
index: './src/index.js',
another: './src/another-module.js',
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
💡
entry
프로퍼티를 작성하면 웹팩에서 자동으로index
와another
를 다른chunk
로 관리를 해서 로딩한다.
웹팩은 둘 간의 의존성(dependency)도 분리를 해서 관리를 하는데, 만약 같은 의존성을 여러entry point
에서 가지고 있다면, 중복된 로딩이 많아져서 성능 저하를 일으킬 수 있다.
중복 되는dependencies
는 다른chunk
로 관리해주는 것이 바람직하다.
출처
https://hoony-gunputer.tistory.com/entry/6편-Code-Splitting-과-React-lazy-Suspense