React.lazy()와 Suspense

1Hoit·2023년 3월 23일
0

React의 버전의 차이

들어가기전
React는 현재도 계속해서 업데이트가 되고 있는 오픈소스 라이브러리이다.

  • React가 버전 18로 업데이트가 되면서 많은 부분이 바뀌었는데, 가장 빠르게 알아볼 수 있는 변화는 콘솔 창에 이전에 보이지 않던 경고문이 보인다.
  • React 18 버전은 더이상 ReactDOM.render를 지원하지 않는다는 내용이다.
  • 저런 경고문이 보이는 이유는 React 18에서는 이제 createRoot API를 사용하기 때문이기 때문인데
    React 18에서 생긴 자동 배칭 또한 이 createRoot API를 사용해야한다.
//React 18이전의 index.js
const rootElement = document.getElementById("root");
ReactDOM.render(<AppTest />, rootElement);
//
//React 18 의 바뀐 index.js
import { createRoot } from "react-dom/client";
const rootElement = document.getElementById("root");
const root = createRoot(rootElement);
root.render(
    <App />
);
  • 지금까지는 Concurrent Mode라고 명명되었지만 React 18버전 부터는 Concurrent Feature로 바뀌면서 하나의 기능으로 들어오게 되었다.

  • 자바스크립트는 싱글 스레드 언어이기 때문에 원래 하나의 작업을 수행할 때 다른 작업 동시에 수행할 수 없으나, concurrent mode를 사용하면 여러 작업을 동시에 처리할 수 있었다.

  • React 18 버전부터는 Concurrent Feature로 바뀌어 하나의 기능으로 들어오게 되었고, 해당 기능 중 하나가 Suspense 기능이다.
  • React 18 버전부터는 이 Suspense 기능을 이용해 독립적으로 렌더링을 할 수 있도록 했다.

React에서의 코드 분할

React는 SPA(Single-Page-Application)인데, 사용하지 않는 모든 컴포넌트까지 한 번에 불러오기 때문에 첫 화면이 렌더링 될때까지의 시간이 오래걸린다.
그래서 사용하지 않는 컴포넌트는 나중에 불러오기 위해 코드 분할 개념을 도입했다.

  • React에서 코드 분할하는 방법은 dynamic import(동적 불러오기)를 사용하는 것이다.
  • 그 전까지는 코드 파일의 가장 최상위에서 import 지시자를 사용해 사용하고자 하는 라이브러리 및 파일을 불러오는 방법을 사용했었다.
    이와같은 방법을 static import(정적 불러오기)라고 한다.

static import

/* 기존에는 파일의 최상위에서 import 지시자를 이용해 라이브러리 및 파일을 불러왔다. */
import moduleA from "library";

form.addEventListener("submit", e => {
  e.preventDefault();
  someFunction();
});

const someFunction = () => {
  /* 그리고 코드 중간에서 불러온 파일을 사용했다. */
}
  • 기존에는 항상 import 구문은 문서의 상위에 위치해야 했고, 블록문 안에서는 위치할 수 없었다.
    왜냐하면 번들링 시 코드 구조를 분석해 모듈을 한 데 모으고 사용하지 않는 모듈은 제거하는 등의 작업을 하는데, 코드 구조가 간단하고 고정이 되어 있을 때에야만 이 작업이 가능해지기 때문이었다.

그러나 이제는 구문 분석 및 컴파일해야 하는 스크립트의 양을 최소화 시키기 위해 dynamic import 구문을 지원한다.

Dynamic Import

form.addEventListener("submit", e => {
  e.preventDefault();
	/* 동적 불러오기는 이런 식으로 코드의 중간에 불러올 수 있다. */
  import('library.moduleA')
    .then(module => module.default)
    .then(someFunction())
    .catch(handleError());
});

const someFunction = () => {
    /* moduleA를 여기서 사용 */
}
  • dynamic import를 사용하게 되면 불러온 moduleA 가 다른 곳에서 사용되지 않는 경우, 사용자가 form을 통해 양식을 제출한 경우에만 가져오도록 할 수 있다.

  • dynamic import는 then 함수를 사용해 필요한 코드만 가져온다.
    가져온 코드에 대한 모든 호출은 해당 함수 내부에 있어야 한다.

  • 이 방식을 사용하면 번들링 시 분할된 코드(청크)를 지연 로딩시키거나 요청 시에 로딩할 수 있다.

  • dynamic import는 React.lazy 와 함께 사용할 수 있다.


React.lazy()

React.lazy 함수를 사용하면 dynamic import를 사용해 컴포넌트를 렌더링할 수 있다

  • React는 SPA(Single-Page-Application)이므로 한 번에 사용하지 않는 컴포넌트까지 불러오는 단점이 있었다
  • 위의 단점을 해결하기 위해 React는 React.lazy를 통해 컴포넌트를 동적으로 import를 할 수 있기 때문에 이를 사용하면 초기 렌더링 지연시간을 어느정도 줄일 수 있다
import Component from './Component';

/* React.lazy로 dynamic import를 감싼다. */
const Component = React.lazy(() => import('./Component'));
  • React.lazy로 감싼 컴포넌트는 단독으로 쓰일 수는 없고, React.suspense 컴포넌트의 하위에서 렌더링을 해야한다.

React.Suspense

Router로 분기가 나누어진 컴포넌트들을 위 코드처럼 lazy를 통해 import하면 해당 path로 이동할때 컴포넌트를 불러오는데 이 과정에서 로딩하는 시간이 생긴다.

  • Suspense는 아직 렌더링이 준비되지 않은 컴포넌트가 있을 때 로딩 화면을 보여주고, 로딩이 완료되면 렌더링이 준비된 컴포넌트를 보여주는 기능이다.
/* suspense 기능을 사용하기 위해서는 import 해와야 합니다. */
import { Suspense } from 'react';

const OtherComponent = React.lazy(() => import('./OtherComponent'));
const AnotherComponent = React.lazy(() => import('./AnotherComponent'));

function MyComponent() {
  return (
    <div>
			{/* 이런 식으로 React.lazy로 감싼 컴포넌트를 Suspense 컴포넌트의 하위에 렌더링한다. */}
      <Suspense fallback={<div>Loading...</div>}>
				{/* Suspense 컴포넌트 하위에 여러 개의 lazy 컴포넌트를 렌더링시킬 수 있다. */}
        <OtherComponent />
				<AnotherComponent />
      </Suspense>
    </div>
  );
}
  • Supense 컴포넌트의 fallback prop은 컴포넌트가 로드될 때까지 기다리는 동안 로딩 화면으로 보여줄 React 엘리먼트를 받는다.
  • Suspense 컴포넌트 하나로 여러 개의 lazy 컴포넌트를 보여줄 수도 있다.

React.lazy와 Suspense의 적용 시기

앱에 코드 분할을 도입할 곳을 결정하는 것은 사실 까다롭기 때문에, 중간에 적용시키는 것보다는 웹 페이지를 불러오고 진입하는 단계인 Route에 이 두 기능을 적용시키는 것이 좋다

import { 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>
);

라우터에 Suspense를 적용하는 방법

  1. 라우터가 분기되는 컴포넌트에서 각 컴포넌트에 React.lazy를 사용하여 import한다.
  2. Route 컴포넌트들을 Suspense로 감싼 후 로딩 화면으로 사용할 컴포넌트를 fallback 속성으로 설정해준다.
  • 주의사항
    초기 렌더링 시간이 줄어드는 분명한 장점이 있지만,
    페이지를 이동하는 과정마다 로딩 화면이 보여지기 때문에 서비스에 따라서 적용 여부를 결정해야 한다.
profile
프론트엔드 개발자를 꿈꾸는 원호잇!

0개의 댓글

관련 채용 정보