[리액트] Code Splitting & Loadable

Jang Seok Woo·2022년 3월 1일
0

리액트

목록 보기
43/58

Loadable Components

Code Spliting이란?

싱글페이지 어플리케이션에서 번들 사이즈가 커지면 로딩속도나 성능면에서 문제가 생길 수 있다. 코드 스플리팅은 이것들을 여러개의 번들로 나누거나 동적으로 import 하는 기법을 말한다.

Loadable Components 란?

React가 자체적으로 제공하는 React.lazy나 React.suspense도 있지만 SSR까지 커버 가능하고 사용방법이 거의 동일한 Ladable Components를 페이스북에서도 추천하고 있다.
이제 간단한 사용방법을 알아보자.

import loadable from '@loadable/component'
const OtherComponent = loadable(() => import('./OtherComponent'))
function MyComponent() {
 return (
  <div>
   <OtherComponent />
  </div>
 )
}

이는 간단한 사용예시이다.

이렇게 하면 OtherComponent는 동적으로 import 된다.
이 말은 즉 해당 컴포넌트가 불러질 때 파일을 읽어온다는 것이다.
해당 파일에 접근하는 순간은 속도가 느려질 수 있으나 초기 속도를 개선할 수 있는 이유이다.
코드 스플리팅을 할때에 어떠한 기준으로 나눌 것이냐에 대한 기준을 세우기가 어려운데 일단은 라우트 기준으로 나누어 보는것을 많은 사람들과 공식문서에서 추천하고 있다.
또한 라이브러리도 아래와 같이 동적으로 가지고 올수 있다.

import loadable from '@loadable/component'
const Moment = loadable.lib(() => import('moment')) // .lib를 사용한다.
function FromNow({ date }) {
 return (
  <div>
   <Moment fallback={date.toLocaleDateString()}>
    {({ default: moment }) => moment(date).fromNow()}
   </Moment>
  </div>
 )
}

용량이 큰 라이브러리등을 초기에 불러오지 않고 필요할 때 불러오는것은 좋은 방법중에 하나이다.(하지만 초기 로딩과 페이지에서 생기는 로딩간에 사용자 경험을 비교해보는 것이 좋을 듯 하다.)
동적 import 도 가능하다.

import loadable from '@loadable/component'
const AsyncPage = loadable(props => import(`./${props.page}`))
function MyComponent() {
 return (
  <div>
   <AsyncPage page="Home" />
   <AsyncPage page="Contact" />
  </div>
 )
}

위와 같이 AsyncPage에서 받은 Props를 사용하여 import 할 수 있습니다.
만약에 babel plugin을 사용하지 않는 경우라면 아래와 같이 cacheKey를 추가해 주어야 한다.

import loadable from '@loadable/component'
const AsyncPage = loadable(props => import(`./${props.page}`), {
cacheKey: props => props.page, //cacheKey를 추가해준다.
})
function MyComponent() {
const [page, setPage] = useState('Home')
 return (
  <div>
   <button onClick={() => setPage('Home')}>Go to home</button>
   <button onClick={() => setPage('Contact')}>Go to contact</button>
  {page && <AsyncPage page={page} />}
  </div>
 )
}

아래와 같이 suspense를 사용하여 fallback처리를 할 수 있다.

import React, { Suspense } from 'react'
import { lazy } from '@loadable/component'
const OtherComponent = lazy(() => import('./OtherComponent'))
function MyComponent() {
 return (
  <div>
   <Suspense fallback={<div>Loading...</div>}>
   <OtherComponent />
   </Suspense>
  </div>
 )
}
suspense 없이도 loadable components에서 fallback처리가 가능하다.
const OtherComponent = loadable(() => import('./OtherComponent'))
function MyComponent() {
 return (
  <div>
   <OtherComponent fallback={<div>Loading...</div>} />
  </div>
 )
}
Error Boundaries 도 설정해줄 수 있다.
import MyErrorBoundary from './MyErrorBoundary'
const OtherComponent = loadable(() => import('./OtherComponent'))
const AnotherComponent = loadable(() => import('./AnotherComponent'))
const MyComponent = () => (
 <div>
  <MyErrorBoundary>
  <section>
   <OtherComponent />
   <AnotherComponent />
  </section>
  </MyErrorBoundary>
 </div>
)

참고 : https://reactjs.org/docs/error-boundaries.html (Error boundaries)

만약 로딩을 지연시키고 싶다면 delay를 사용하면 된다.(“p-min-delay”사용)

import loadable from '@loadable/component'
import pMinDelay from 'p-min-delay'
// Wait a minimum of 200ms before loading home.
export const OtherComponent = loadable(() =>
pMinDelay(import('./OtherComponent'), 200)
)
time out 도 이용할 수 있다. (“promise-timeout”사용)
import loadable from '@loadable/component'
import { timeout } from 'promise-timeout'
// Wait a maximum of 2s before sending an error.
export const OtherComponent = loadable(() =>
timeout(import('./OtherComponent'), 2000)
)

preload라는것이 있는데 예를 들어 어떠한 버튼을 클릭하면 불러와지는 컴포넌트가 있다고 하면 그것을 hover했을때 미리 불러오기를 시작할 수 있다.

import loadable from '@loadable/component'
const Infos = loadable(() => import('./Infos'))
function App() {
const [show, setShow] = useState(false)
return (
 <div>
   <a onMouseOver={() => Infos.preload()} onClick={() => setShow(true)}>
  Show Infos
   </a>
  {show && <Infos />}
 </div>
 )
}
loadable/babel-plugin 설치
babel plugin
yarn add -D @loadable/babel-plugin
babel config 에 추가
{
 "plugins": ["@loadable/babel-plugin"]
}
tsconfig.json
{
...
"module":"esnext"
...
}

라우터 기준으로 스플리팅with typescript

yarn add @types/loadable__component // with typescript

라우터를 객체로 관리하는 분들이 많을거라 생각하여 type지정을 어떻게 하였는지 예시를 기록한다.

export type CustomRoutes<T = any> = RouteProps & {
key: string;
path: string;
component: LoadableComponent<T>;
exact?: boolean;
// 등등 원하는 타입을 설정한다.
} & T;
사용
interface Props {
routes: CustomRoutes[];
}
const Routes: React.FC<Props> = ({ routes }) => {
return (
<Switch>
{routes?.map((route: RoutesProps, key) => (
  <AuthRoute
  key={String(key)}
  exact={route.exact ?? false}
  path={route.path}
  component={route.component}
  />
))}
</Switch>
);
};

커스텀 fallback

import baseLoadable from '@loadable/component'
function loadable(func) {
return baseLoadable(func, { fallback: <div>Loading...</div> })
}
const OtherComponent = loadable(() => import('./OtherComponent'))

위와 같이 baseLoadable을 가지고 커스터마이징 할 수 있다.
지원 버전
React v16.3+
webpack v4.6+
v5.x(React v16.3+): IE11, IE 9+(Map + Set 폴리필 포함), 모든 Evergreen브라우저
(Evergreen 브라우저에는 운영 체제 버전에 관계없이 업데이트할 수 있는 Chrome 및 Firefox(및 파생 제품)가 포함됩니다. 지난 몇 년 동안의 모든 버전이 관련 API를 지원하기 때문에 Edge와 Safari도 모두 잘 작동합니다.)

참고 : https://medium.com/greendatakr/loadable-components-881e936aa8fa

profile
https://github.com/jsw4215

0개의 댓글