Code-splitting

Code splitting

리액트와 같은 SPA 앱 을 만들때는 한번에 모든 페이지에 대한 리소스를 다운로드 받아야 하기 때문에 번들링 사이즈가 커지게 된다. 이렇게 처음 진입할때 모든 리소스를 다운 받으면 다운 받기 까지 시간도 많이 걸리고, 특히 네트워크 상황이 좋지 않은 곳에서는 좋지 않은 사용자 경험을 줄수 도 있다.

code splitting 은 번들링 된 부분의 코드를 분활하여 필요할때 분활한 리소스를 다운 받도록 하는 기술이다. React 에서 Code splitting 을 하기 위해서 어떻게 해야 하는지 살펴 본다.

Import

먼저 import() 를 사용한다. import 문은 thenable 을 채택하기 때문에 import 가 완료됬을때 then 을 통해 특정한 작업을 하도록 할 수 있다. 예를들어 특정 컴포넌트를 불러 오는 작업이 끝났을때 특정 작업을 하다록 다음과 같이 작성할 수 있다.

1
2
3
import('./components/SomeComponent').then(() => {
console.log('특정 컴포넌트 불러오기 완료')
})

React.lazy

React.lazy 를 사용하면 필요할때 컴포넌트를 불러오는 작업을 할 수 있다. lazy 는 콜백을 받는 함수인데 콜백 안에서 import() 를 실행하면 된다.

1
React.lazy(() => import('./components/SomeComponent'));

이렇게 하면 SomeComponent 에 관한 리소스는 따로 분활하고 컴포넌트가 필요한 시점에 리소스를 다운받게 된다.

어디에서 진행해야 할까?

그렇다면 Code splitting 은 어느 단위, 어디에서 진행해야 할까? 어떤 답이 있는것은 아니지만, 사용자에 행동에 비추어 봤을대 페이지 단위로 코드를 분활하는것이 좋다. 사용자들은 특정 페이지에서 다시 리소스를 다운받아서 기다리는것은 오래 걸린다고 생각하겠지만, 어떤 페이지를 넘어갈때에는 상대적으로 그럴 수 있다고 생각하기 때문이다. 페이지 컴포넌트를 불러오는 시점에서 Code splitting 을 해주도록 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import React, { Suspense } from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";

const LongPage = React.lazy(() => import("./components/LongPage"));
const ShortPage = React.lazy(() => import("./components/ShortPage"));

function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<BrowserRouter>
<Routes>
<Route path="/" element={<ShortPage />} />
<Route path="/long" element={<LongPage />} />
</Routes>
</BrowserRouter>
</Suspense>
);
}

export default App;

보통 페이지 컴포넌트를 불러오는 시점은 React-router 을 사용하는 시점이다. 다음과 같이 작성하면 페이지에 들어갈때, 리소스를 따로 다운 받는 것을 확인할 수 있다.

브라우저 개발자 도구를 통해 확인한 모습 :

처음 / 페스로 접근했을때 src_components_ShortPage_js.chunk.js 를 다운 받고, /long 패스로 접근했을때 src_components_LongPage_js.chunk.js를 다운 받았다.

Suspense

코드에서 Suspense 를 사용하는것을 볼 수 있다. 이는 리소스를 다운 받을때 데체 컨텐츠로 fallback 에 넘겨준 컴포넌트를 사용하겠다는 것이다. 코드를 분활했기 때문에 리소스를 다운로드를 받을 수 밖에 없고, 이때는 필연적으로 빈 컨텐츠를 볼것이기 때문에 대체 컨텐츠를 명시한 것이다.

여담으로 useTransition 을 사용하면 오래 걸리는 작업을 뒤로 미룰수 도 있다. 만약 특정 컨텐츠 안에서 분활된 리소스를 다운받는 작업을 트리거 하는 함수 가있다면 이 함수를 useTransition 에 리턴값에 두번째 값으로 얻어지는 함수로 감싸서 나중에 처리하도록 할 수 있다. 코드 분할 예제는 아니지만 useTransition 을 사용한 예제는 다음과 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import React, { useCallback, useState, useTransition } from "react";
import { Link } from "react-router-dom";

function LongPage() {
const [value, setValue] = useState("");
const [isPending, startTransition] = useTransition();

const handleChange = (e) => {
startTransition(() => {
setValue(e.target.value);
});
};

return (
<>
<Link to={"/"}>short</Link>
<input type="text" value={value} onChange={handleChange} />
{isPending ? (
<div>Loading...</div>
) : (
Array(10000)
.fill(0)
.map(() => <div>{value}</div>)
)}
</>
);
}

export default LongPage;