코드 스플릿팅은 단어 그대로 코드를 분할한다는 의미 입니다.
여기서 코드를 분할 하는 것은 번들 코드를 분할한다고 생각하시면 됩니다.
React와 같은 SPA 을 개발하고 나면 webpack 같은 번들러로 코드를 번들하고, html 파일에서 번들된 자바스크립트 파일을 불러와서 웹앱을 브라우저에서 실행하는게 일반적입니다.
그런데 번들 파일이 다 불러와져야 웹앱이 실행되다 보니, 웹앱의 크기가 커지면 커질 수록 의도치 않게 성능에 악영향을 끼치게 됩니다.
특히 서드 파티 라이브러리같은 경우 그 크기가 굉장히 큰 경우가 많기 때문에 번들 파일의 크기도 금방금방 커져버립니다.
그런 때에 고려해야 할 방법이 바로 코드 분할입니다.
이는 번들 파일의 코드를 분할하여, 모든 코드를 한 번에 불러오지 않고 사용자가 필요로 할 때에 필요한 코드만 불러오는 개념입니다.
React 에 적용해서 설명해 보자면, 리액트는 CSR 로써 내가 만든 코드를 번들러로 하나의 JS 파일로 만들어 주며 웹앱이 작동할 때 앞서 말한 JS 파일을 서버에서 클라이언트에게 보내주고 실행하는 구조입니다.
하지만 코드 분할은 내가 만든 코드를 하나의 JS 파일로 만드는 것이 아닌 여러개의 JS 파일로 분할해 주고 페이지마다 필요한 JS 파일을 그때 그때 클라이언트에서 실행하는 방법이라고 생각하시면 됩니다.
쉽게 말해서 Dynamic import 는 위에 코드 스플릿팅을 도와주는 아주 유용한 방법입니다.
Dynamic import를 사용하면 필요한 코드만 로드합니다.
동적 임포트를 사용하면 애플리케이션 초기 로딩 시점에 모든 코드를 로드하지 않고, 사용자가 특정 기능이나 페이지로 이동할 때 해당 기능이나 페이지에 필요한 코드만 로드할 수 있습니다. 이를 통해 초기 로딩 시간을 줄이고 필요한 코드만 로드하여 리소스 사용량을 최적화할 수 있습니다.
또한 청크 분할을 해줍니다. 동적 임포트를 사용하여 특정 모듈이나 리소스를 분할된 청크(chunk)로 만들 수 있습니다. 이 청크들은 각각의 독립된 자바스크립트 파일로 번들됩니다. 따라서 필요한 페이지로 이동할 때 필요한 청크만 다운로드되고 실행되어 성능을 향상시킬 수 있습니다.
하지만 React에서 컴포넌트 파일을 정의하고 동적 불러오기를 사용하면 에러가 발생합니다. 그럼 React 에서는 동적 불러오기 기능을 사용할 수 없는 걸까요?
React.lazy 는 이러한 동적 불러오기를 React에서 사용할 수 있게 도와줍니다.
React.lazy로 불러온 컴포넌트는 단독으로 쓰일 수 없고,
React.Suspense 컴포넌트로 하위에서 렌더링되어야 합니다.
해당 페이지가 로딩되는 동안 Suspense에서 fallback 으로 만든
Loading... 부분이 실행되고 로딩이 끝난 시점에 페이지가 보여집니다.
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} exact />
<Route path="/about" component={About} exact />
</Switch>
</Suspense>
</Router>
);
여기서 exact 는 무엇일까요??
React Router의 컴포넌트에서 exact prop은 경로와 정확히 일치하는 경우에만 해당 라우트가 렌더링되도록 지정하는 옵션입니다.
일반적으로 React Router에서 경로 매칭은 "부분 일치"에 따라 동작합니다. 예를 들어, /users와 /users/:id라는 두 개의 경로가 있는 경우, /users/:id 경로도 /users와 부분적으로 일치하기 때문에 /users로 접근하면 /users/:id 경로에 해당하는 컴포넌트도 함께 렌더링됩니다.
그러나 exact prop을 사용하면 이러한 부분 일치를 방지하고 정확히 일치하는 경우에만 라우트가 렌더링됩니다. 즉, exact prop이 있는<Route>
는 경로가 주어진 경로와 정확히 일치할 때만 해당 컴포넌트가 렌더링됩니다. 따라서 주어진 경로와 정확하게 일치하지 않는 경우 해당<Route>
는 무시됩니다.
결론부터 말하자면 리액트 프로젝트에서 항상 동적 임포트를 사용하는 것이 항상 더 좋은 선택인지는 아닙니다. 동적 임포트를 사용하는 것은 프로젝트의 구조와 규모, 그리고 사용자 경험에 따라 다를 수 있습니다.
일반적으로 작은 규모의 프로젝트나 페이지 간에 코드 공유가 많은 경우에는 동적 임포트를 적용하는 것이 초기 로딩 시간을 최적화하고 성능을 향상시킬 수 있습니다.
🍎 공통된 레이아웃 및 구조 🍎
여러 페이지가 동일한 레이아웃이나 구조를 가지고 있는 경우에는 해당 레이아웃이나 구조를 구현하는 코드가 각 페이지에서 공유될 수 있습니다. 예를 들어, 모든 페이지가 헤더, 사이드바, 푸터와 같은 공통된 레이아웃을 사용하는 경우, 이러한 레이아웃을 구현하는 코드가 페이지 간에 공유될 수 있습니다.
🍎 공통된 컴포넌트 🍎
여러 페이지에서 사용되는 컴포넌트가 있는 경우에는 해당 컴포넌트의 코드가 페이지 간에 공유될 수 있습니다. 예를 들어, 여러 페이지에서 사용되는 버튼, 입력 필드, 모달 창과 같은 컴포넌트는 해당 컴포넌트의 코드가 페이지 간에 공유될 수 있습니다.
🍎 서비스 로직 🍎
여러 페이지에서 사용되는 서비스 로직이 있는 경우에는 해당 서비스 로직의 코드가 페이지 간에 공유될 수 있습니다. 예를 들어, 여러 페이지에서 사용되는 사용자 인증, 데이터 가져오기, 상태 관리와 같은 서비스 로직은 해당 로직의 코드가 페이지 간에 공유될 수 있습니다.
🍎 유틸리티 함수 및 헬퍼 함수 🍎
여러 페이지에서 사용되는 유틸리티 함수나 헬퍼 함수가 있는 경우에는 해당 함수들의 코드가 페이지 간에 공유될 수 있습니다. 예를 들어, 날짜 포맷 변환, 문자열 조작, 데이터 변환과 같은 유틸리티 함수는 해당 함수의 코드가 페이지 간에 공유될 수 있습니다.
하지만 더 큰 규모의 프로젝트나 페이지 간에 코드 공유가 적은 경우에는 동적 임포트를 사용할 필요가 없을 수 있습니다.
🍎 페이지 간의 기능이 상이한 경우 🍎
각 페이지가 서로 다른 기능을 제공하거나 서로 다른 컨텍스트에서 작동하는 경우에는 해당 페이지 간에 코드 공유가 적을 수 있습니다. 예를 들어, 블로그 게시물 목록 페이지와 블로그 게시물 상세 페이지는 서로 다른 기능을 가지고 있으며, 공통된 코드가 적을 수 있습니다.
🍎 동적 라우팅이 많은 경우 🍎
페이지 간에 동적으로 라우팅되는 경우, 특정 페이지에 필요한 로직이 다른 페이지에서 사용되지 않을 수 있습니다. 예를 들어, 전자상거래 사이트의 제품 목록 페이지와 제품 상세 페이지는 각각 다른 제품에 대한 정보를 표시하므로 공통된 로직이 적을 수 있습니다.
🍎 다양한 페이지 유형이 있는 경우 🍎
웹 애플리케이션이 다양한 유형의 페이지를 가지고 있는 경우, 각 페이지 간에 공통된 로직이 적을 수 있습니다. 예를 들어, 뉴스 웹사이트에서 뉴스 기사 페이지와 검색 결과 페이지는 서로 다른 유형의 페이지이며, 공통된 로직이 적을 수 있습니다.
🍎 기능별로 모듈화가 잘 되어 있는 경우 🍎
프로젝트가 기능별로 모듈화되어 있고, 각 페이지가 필요한 모듈을 독립적으로 로드할 수 있는 경우에는 페이지 간에 코드 공유가 적을 수 있습니다. 이러한 경우에는 필요한 모듈만 로드하여 초기 로딩 시간을 최적화할 수 있습니다.
또한, 동적 임포트를 사용할 때 주의할 점도 있습니다. 너무 많은 코드를 동적으로 로드하면 오히려 초기 로딩 시간이 늘어나고 사용자 경험이 저하될 수 있습니다. 따라서 동적 임포트를 적용할 때에는 어떤 코드를 동적으로 로드할지 신중하게 결정해야 합니다.