React 16.6 버전에서는 코드를 불러오는 동안 “기다릴 수 있고”, 기다리는 동안 로딩 상태(스피너와 같은 것)를 선언적으로 지정할 수 있도록 <Suspense> 컴포넌트가 추가되었습니다.
const ProfilePage = React.lazy(() => import('./ProfilePage')); // 지연 로딩
// 프로필을 불러오는 동안 스피너를 표시합니다
<Suspense fallback={<Spinner />}>
<ProfilePage />
</Suspense>
데이터를 가져오기 위한 Suspense는 <Suspense>를 사용하여 선언적으로 데이터를 비롯한 무엇이든 “기다릴” 수 있도록 해주는 새로운 기능입니다. 이 페이지에서는 사용 사례 가운데 데이터 로딩에 초점을 두지만, 이 기능은 이미지, 스크립트, 그 밖의 비동기 작업을 기다리는 데에도 사용될 수 있습니다.
Suspense를 사용하면 컴포넌트가 렌더링되기 전까지 기다릴 수 있습니다. 이 예시에서는 두 컴포넌트가 데이터를 불러오는 비동기 API 호출을 기다립니다.
const resource = fetchProfileData();
function ProfilePage() {
return (
<Suspense fallback={<h1>Loading profile...</h1>}>
<ProfileDetails />
<Suspense fallback={<h1>Loading posts...</h1>}>
<ProfileTimeline />
</Suspense>
</Suspense>
);
}
function ProfileDetails() {
// 비록 아직 불러오기가 완료되지 않았겠지만, 사용자 정보 읽기를 시도합니다
const user = resource.user.read();
return <h1>{user.name}</h1>;
}
function ProfileTimeline() {
// 비록 아직 불러오기가 완료되지 않았겠지만, 게시글 읽기를 시도합니다
const posts = resource.posts.read();
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.text}</li>
))}
</ul>
);
}
React.lazy 를 적용하는 곳
React 공식문서의 코드 분할 항목에 의하면, Router 바로 아래에 Suspense를 위치시키고, Route로 보여줄 컴포넌트들을 React.lazy로 불러올 것을 권장하고 있다.
아래가 그 예시 코드이다.
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}/>
<Route path="/about" component={About}/>
</Switch>
</Suspense>
</Router>
);
https://ko.reactjs.org/docs/code-splitting.html
https://velog.io/@code-bebop/dynamic-import%EC%99%80-React.lazy
SSR에서의 코드 분할
React.lazy는 SSR에서 작동하지 않는다.
SSR 웹 앱에서 코드 분할을 하고 싶다면 @loadable/component 라이브러리를 사용해야한다. 이는 React 공식문서에서도 권장하는 바이다.
React.lazy와 Suspense는 아직 서버 사이드 렌더링을 할 수 없습니다. 서버에서 렌더링 된 앱에서 코드 분할을 하기 원한다면 Loadable Components를 추천합니다. 이는 서버 사이드 렌더링과 번들 스플리팅에 대한 좋은 가이드입니다.
dynamic import
왜 사용하는가?
dynamic import는 동적 불러오기이다. 기존에 코드 파일의 가장 상위에서 import 구문을 사용하여 불러오는 것은 static import(정적 불러오기)라고 한다.
정적 불러오기 같은 경우 문서의 가장 상위에 위치해야하고(바닐라 js에선 맨 밑에 위치해도 되지만, react-app에선 컴파일 에러가 발생한다), 블록문 안에 위치할 수 없는 등의 제약 사항이 있다.
JAVASCRIPT.INFO의 동적으로 모듈 가져오기 문서에서는 정적 불러오기에 이런 제약사항이 발생한 이유에 대해 이렇게 서술하고 있다.
이런 제약사항이 만들어진 이유는 import/export는 코드 구조의 중심을 잡아주는 역할을 하기 때문입니다. 코드 구조를 분석해 모듈을 한데 모아 번들링하고, 사용하지 않는 모듈은 제거(가지치기)해야 하는데, 코드 구조가 간단하고 고정되어있을 때만 이런 작업이 가능합니다.
이런 장점들을 내려놓고서라도 동적 불러오기를 사용해야 하는 이유가 바로 코드 분할이다.
사용법
동적 불러오기는 다음과 같이 사용한다.
import('./sum').then(sum => {
console.log(sum(1 + 2));
});
동적 불러오기는 import() 구문을 사용하는데, 프로미스 객체를 반환한다. 프로미스 객체의 반환값은 불러온 모듈이다. 함수를 호출하는 문법을 취하고 있으나, import는 함수가 아니다.
동적 불러오기는 코드의 위치에 관계없이 사용이 가능하기 때문에, 모듈들을 사용자가 필요로 할 때에 불러오게끔 할 수 있다.
code splitting
code splitting은 코드 분할이다. 어떤 코드를 분할하느냐? 바로 번들 코드를 분할한다.
React같은 SPA웹앱을 개발하고 나면 webpack같은 번들러로 코드를 번들하고, html 파일에서 번들된 자바스크립트 파일을 불러와서 웹앱을 브라우저에서 실행한다.
그런데 번들 파일이 다 불러와져야 웹앱이 실행되다 보니, 웹앱의 크기가 커지면 커질 수록 성능에 악영향을 끼치게 된다. 특히 서드 파티 라이브러리같은 경우 그 크기가 굉장히 큰 경우가 많기 때문에 번들 파일의 크기도 금방금방 커져버린다.
그런 때에 고려할 사안이 바로 코드 분할이다. 이는 번들 파일의 코드를 분할하여, 모든 코드를 한 번에 불러오지 않고 사용자가 필요로 할 때에 필요한 코드만 불러오는 개념이다.
React.lazy
왜 사용하는가?
React에서 컴포넌트 파일을 정의하고 동적 불러오기를 사용하면 에러가 발생한다.
컴포넌트를 동적으로 불러오기위해선 React.lazy를 사용해야한다.
사용법
React.lazy를 사용한 예시 코드이다.
import { Suspense } from 'react';
const SomeComponent = React.lazy(() => import('./SomeComponent'));
const MyComponent = () => {
return (
<Suspense fallback={<div>로딩 중. . .</div>}>
<SomeComponent />
</Suspense>
);
}
React.lazy() 는 import() 구문을 반환하는 콜백함수를 인자로 받는다. 동적 불러오기로 불러와지는 모듈은 1. ReactComponent를 포함하며 2. default export를 가진 모듈이어야 한다. 그리고 불러온 컴포넌트를 반환한다.
React.lazy로 불러온 컴포넌트는 단독으로 쓰일 수 없고, React.Suspense 컴포넌트로 하위에서 렌더링되어야 한다.
Suspense 컴포넌트는 fallback prop을 필수로 가진다. fallback prop은 로딩 표시기로 사용할 컴포넌트를 받는다.
'💎 React' 카테고리의 다른 글
pakage-lock.json 과 yarn.lock (0) | 2022.05.02 |
---|---|
useStyle makeStyle keyframes에다가 props 로 넘겨주는 법 (0) | 2022.04.18 |
아폴로 클라이언트 (0) | 2022.03.25 |
리액트 Context API, reducer (0) | 2022.03.24 |
React - reducer (0) | 2022.03.08 |