코드분할 왜 해야 하는가?
번들링은 필수이나 앱이 커지면 번들도 커지기 마련입니다.
특히, 용량이 큰 서드파티 라이브러리는 로드시간을 길게 만드는 주범이니 주의합시다.
아이러니하게도, 번들이 거대해질수록 번들을 나누어야 합니다. 이럴꺼면 왜 번들링한건데 ㅡ.ㅡ
번들한 코드를 분리하고 나서, 필요에 따라 특정 컴포넌트만 로딩하거나 병렬로 로딩 할 수 있습니다.
코드분할의 장점
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만 지원합니다.