- Published on
React.lazy 실패 시 retry하기
React에서 Page 단위로 JS 번들을 분리할 때 다음과 같이 Page 컴포넌트 번들의 lazy loading을 구현한다.
import React, { lazy } from 'react';
const MenuMain = lazy(() => import(/* webpackChunkName: "MenuMain" */ '@pages/MenuMain')
// ...
export default function Menu() {
return (
<Switch>
<Route path={ROUTES.MENU} component={MenuMain} />
</Switch>
);
}
React.lazy 실패 처리
그런데, 초기 번들파일을 로드한 이후 중간에 Network연결이 끊긴 상태에서 페이지 이동을 한다면?
해당 페이지의 번들파일 lazy load에 실패하고, 사용자는 에러화면을 보게 될 것이다.
이런 경우 단순하게 사용자가 Network 연결 후 새로고침을 하게 할 수도 있지만, 그러면 진행했던 flow가 날라가게 되거나 모바일 웹뷰에서는 사용자 경험 측면에서 좋지 않은 방법이 될 것이다.
그러면 어떤 해결책이 있을까?
App 전체를 refresh 하지 않고, 실패한 해당 페이지의 번들 로드만 네트워크 연결후에 다시 retry하여 진행하고 있는 flow를 그대로 진행할 수 있게 해주어야 한다.
retryableLazy 구현
아래의 retryableLazy는 다음과 같이 동작한다.
- lazy load 를 수행하는 lazyImport와 lazy component를 set하는 setComponent를 Param으로 전달받는다.
- React.lazy 컴포넌트를 생성하여 component 변수에 할당한다. (setComponent를 통해서)
- import promise가 reject 되면 Network Error Page를 띄우고, 재시도 버튼을 눌렀을 때 호출할 콜백함수를 전달한다.
- 3번의 재시도 콜백함수가 호출되면 retryableLazy를 재귀로 호출하여 2번을 다시 수행한다.
- component에 새로운 React.lazy 컴포넌트가 할당되고 페이지를 re-rendering한다.
import React, { lazy } from 'react';
import { ErrorUtility, logger } from '@utils';
type IDynamicImportType = () => Promise<{ default: React.ComponentType<any> }>;
type ISetComponentType = (component: React.ComponentType) => void;
export default function retryableLazy(lazyImport: IDynamicImportType, setComponent: ISetComponentType) {
setComponent(
lazy(
() =>
lazyImport().catch((error) => {
logger.error(error, 'Dynamic component load error');
ErrorUtility.showNetworkErrorPage(() => {
retryableLazy(lazyImport, setComponent);
});
}) as ReturnType<IDynamicImportType>
)
);
}
retryableLazy 사용
처음의 lazy load 코드는 retryableLazy를 사용하여 다음과 같이 변경된다.
import React from 'react';
import { retryableLazy } from '@components-common/lazyLoad';
let MenuMain;
retryableLazy(
() => import(/* webpackChunkName: "MenuMain" */ '@pages/MenuMain'),
(component) => {
MenuMain = component;
}
);
// ...
export default function Menu() {
return (
<Switch>
<Route path={ROUTES.MENU} component={MenuMain} />
</Switch>
);
}