코드분할 왜 해야 하는가?
번들링은 필수이나 앱이 커지면 번들도 커지기 마련입니다.
특히, 용량이 큰 서드파티 라이브러리는 로드시간을 길게 만드는 주범이니 주의합시다.
아이러니하게도, 번들이 거대해질수록 번들을 나누어야 합니다. 이럴꺼면 왜 번들링한건데 ㅡ.ㅡ
번들한 코드를 분리하고 나서, 필요에 따라 특정 컴포넌트만 로딩하거나 병렬로 로딩 할 수 있습니다.
코드분할의 장점
1) 초기 로딩이 빠르다
: 코드 총량은 일정하나 초기 로딩에 필요한 코드는 적어진다
2) 지연 로딩이 가능하다
: 결국엔 초기 로딩이 빨라진다
동적 import
적용전
import { add } from './math'; console.log(add(16, 26));
동적 import 적용후
import("./math").then(math => { console.log(math.add(16, 26)); });
동적 import는 불러온 모듈을 프로미스 객체로 반환한다.
코드의 위치에 관계 없이 동적 import를 할 수 있다.
Webpack이 이 구문을 만나게 되면 앱의 코드를 분할합니다. Create React App을 사용하고 있다면 이미 Webpack이 구성이 되어 있기 때문에 즉시 사용할 수 있습니다. Next.js 역시 지원합니다.
Babel을 사용할 때는 Babel이 동적 import를 인식할 수 있지만 변환하지는 않도록 합니다. 이를 위해 @babel/plugin-syntax-dynamic-import를 사용하세요.
React.lazy
import React, { Suspense } from 'react';
import MyErrorBoundary from './MyErrorBoundary';
const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));
const MyComponent = () => (
<div>
<MyErrorBoundary>
<Suspense fallback={<div>Loading...</div>}>
<section>
<OtherComponent />
<AnotherComponent />
</section>
</Suspense>
</MyErrorBoundary>
</div>
);
동적 import를 사용하여 컴포넌트를 렌더링 한다.
매개변수로 동적 import()를 호출하는 함수를 받는다.
React.lazy 컴포넌트는 Suspense 컴포넌트 하위에 속해야 한다.
Suspense 컴포넌트는 fallback prop은 lazy 컴포넌트를 로딩하는 동안 렌더링하는 요소이다.
하나의 Suspense 컴포넌트로 여러 lazy 컴포넌트를 감쌀 수 있다.
Suspense를 미리 정의한 MyErrorBoundary 컴포넌트로 감싸면
lazy 컴포넌트에서 네트워크 장애가 발생 했을시 에러를 처리할 수 있다
Error boundaries로 감싼 컴포넌트들의 에러만을 캐치한다
클래스 컴포넌트만이 에러 경계가 될 수 있다
try catch 구문과 비슷하지만, 컴포넌트에만 적용되며 Error boundaries가 에러를 캐치하지 못하면 에러는 그 위의 가장 가까운 Error boundaries로 전파된다
Error boundaries 캐치하지 않는 에러
Error boundaries의 자식이 아닌 자체에서 발생하는 에러
Error boundaries레퍼런스
class MyErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
// 다음 렌더링에서 폴백 UI가 보이도록 상태를 업데이트 합니다.
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
// 에러 리포팅 서비스에 에러를 기록할 수도 있습니다.
logErrorToMyService(error, errorInfo);
}
render() {
if (this.state.hasError) {
// 폴백 UI를 커스텀하여 렌더링할 수 있습니다.
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
https://ko.reactjs.org/docs/error-boundaries.html
라우터에서의 코드분할
코드분할에 가장 적합한곳은 라우트이다. 페이지 전환시에 로딩이 필요하다.
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
const Home = lazy(() => import('./routes/Home'));
const About = lazy(() => import('./routes/About'));
const App = () => (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
Named Exports
// ManyComponents.js
export const MyComponent = /* ... */;
export const MyUnusedComponent = /* ... */;
// MyComponent.js
export { MyComponent as default } from "./ManyComponents.js";
// MyApp.js
import React, { lazy } from 'react';
const MyComponent = lazy(() => import("./MyComponent.js"));
React.lazy는 현재 default exports만 지원합니다.