TIL: React | [공식문서 고급 안내서] 번들링, 코드 분할 (lazy, transition, error boundaries, route) - 221227

Lumpen·2022년 12월 27일
0

React 공식문서

목록 보기
11/13

번들링

대부분 React 앱들은 webpack, vite 같은 툴을 사용하여
여러 파일을 하나로 병합한 번들 된 파일을 웹페이지에 포함하여 한 번에 앱을 로드한다

예시

App.js

import { add } from '.math.js';

console.log(add(1, 2));

math.js

export function add(a, b) {
	return  a + b;
}

번들

function add(a, b) {
	return a + b;
}
console.log(add(1, 2));

실제 번들은 이렇지 않지만 이런 느낌

webpack 같은 툴을 이용하지 않으면 스스로 번들링을 해야하기 때문에 사용하는 편이 좋다

코드 분할

번들링은 훌륭하지만 앱이 커지면 번들도 커진다
특히 서드 파티 라이브러리를 추가할 때 앱이 커지는 것을
유의해야 한다

번들이 거대해지는 것을 방지하기 위한 방법은 번들을 나누는 것
코드 분할은 런타임에 여러 번들을 동적으로 만들고 불러오는 것으로 webpack 등의 번들러가 지원하는 기능이다

코드 분할은 앱을 지연 로딩하게 도와주고 사용자게에 획기적인 성능 향상을 준다
앱의 코드 양을 줄이지 않고도 필요하지 않은 코드를 불러오지 않게 하여 초기 로딩에 필요한 비용을 줄인다

import()

코드 분할을 도입하는 가장 좋은 방법은 동적 import() 문법을 사용하는 것

before

import { add } from './math';

console.log(add(16, 26));

after

import("./math").then(math => {
  console.log(math.add(16, 26));
});

webpack 이 이 구문을 만나게 되면 앱의 코드를 분할한다
Babel을 사용할 때는 바벨이 동적 import를 인식할 수 있지만
변환하지 않도록 해야한다
-> @babel/plugin-syntax-dynamic-import를 사용

React.lazy()

React.lazy() 함수를 사용하면 동적 import를 사용하여 컴포넌트를 렌더링 할 수 있다

before

import OtherComponent from './OtherComponent';

after

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

컴포넌트가 처음 렌더링 될 때 OtherComponent를 포함한 번들을 자동으로 불러온다
React.lazy() 는 동적 import()를 호출하는 함수를 인자로 가진다
React 컴포넌트를 default expor로 가진 모듈 객체가
이행되는 Promise를 반환한다

lazy 컴포넌트는 Suspense 컴포넌트 하위에서 렌더링 되어야 한다
Suspense는 lazy 컴포넌트가 로드되길 기다리는 동안 로딩 화면과 같은 예비 컨텐츠를 보여줄 수 있다

import React, { Suspense } from 'react';

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

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <OtherComponent />
      </Suspense>
    </div>
  );
}

fallback prop 은 컴포넌트가 기다리는동안 렌더링할 React 엘리먼트를 받는다
Suspense 컴포넌트는 여러 lazy 컴포넌트, React 엘리먼트를 감쌀 수 있다

import React, { Suspense } from 'react';

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

function MyComponent() {
  return (
    <div>
      <Suspense fallback={<div>Loading...</div>}>
        <section>
          <OtherComponent />
          <AnotherComponent />
        </section>
      </Suspense>
    </div>
  );
}

Named Exports

React.lazy는 현재 default exports만 지원합니다. named exports를 사용하고자 한다면 default로 이름을 재정의한 중간 모듈을 생성할 수 있습니다. 이렇게 하면 tree shaking이 계속 동작하고 사용하지 않는 컴포넌트는 가져오지 않습니다.
// 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"));

Avoiding fallbacks (폴백 피하기)

렌더링의 결과로 이미 사용자에게 표시된 구성요소를 포함하여 모든 구성요소가 일시 중단될 수 있다
화면 내용이 항상 일관성을 유지하기 위해 이미 표시된 구성 요소가 일시 중단된 경우
React는 트리를 가장 가까운 경계까지 숨겨야 한다
그러나 사용자의 관점에서 이것은 방향을 잃을 수 있다

import React, { Suspense } from 'react';
import Tabs from './Tabs';
import Glimmer from './Glimmer';

const Comments = React.lazy(() => import('./Comments'));
const Photos = React.lazy(() => import('./Photos'));

function MyComponent() {
  const [tab, setTab] = React.useState('photos');
  
  function handleTabSelect(tab) {
    setTab(tab);
  };

  return (
    <div>
      <Tabs onTabSelect={handleTabSelect} />
      <Suspense fallback={<Glimmer />}>
        {tab === 'photos' ? <Photos /> : <Comments />}
      </Suspense>
    </div>
  );
}

이 예에서 탭이 'Photos'에서 'Comments'로 변경되어도
'Commnets' 컴포넌트가 일시 중단된 경우 사용자는 깜박임을 볼 수 있다
사용자가 더 이상 'Photos'를 보고 싶지 않고,
'Comments' 구성 요소가 아무것도 렌더링할 준비가 되지 않았으며,
리액트는 사용자 경험을 일관성 있게 유지해야 하므로
위의 'Glimmer'를 보여줄 수밖에 없기 때문이다

그러나 이러한 사용자 환경이 바람직하지 않은 경우도 있다
특히 새 UI를 준비하는 동안에는 '오래된' UI를 보여주는 것이 좋을 때가 있다
새로운 startTransition API를 사용하여 React에서 이 작업을 수행할 수 있다

로딩스패너를 띄우는 것이 항상 좋은 것은 아니다

startTransition API

function handleTabSelect(tab) {
  startTransition(() => {
    setTab(tab);
  });
}

handleTabSelect 함수에서 탭을 변경하는 setStat() 함수를
긴급 업데이트가 아니라 시간이 걸릴 수 있는 전환으로 만든다
React의 setState() 함수는 기본적으로 긴급 업데이트
그다음 React는 이전 UI를 제자리에 유지하고 대화형으로 유지하며
준비되면 를 표시하는 것으로 화면을 전환한다
자세한 내용은 transition 참조

Error boundaries

네트워크 장애 같은 이유로 다른 모듈 로드에 실패했을 경우 에러가 발생할 수 있다
이 때 Error boundaries를 이용하여 사용자 경험, 복구 관리를 할 수 있다
Error boundaries를 만들고 lazy 컴포넌트로 감싸면
네트워크 장애 발생 시 에러를 표시할 수 있다

import React, { Suspense } from 'react';
import ErrorBoundary from './ErrorBoundary';

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

const Component = () => (
	<div>
    	<ErrorBoundary>
          <Suspense fallback={<div>loading..</div>}>
            <div>
            	<OtherComponent />
            </div>
          </Suspense>
        </ErrorBoundary>
    </div>
);

Route-based code splitting

앱에 코드 분할을 어느 곳에 도입할지 결정하는 것은 까다롭다
사용자 경험을 해치지 않으면서 균등하게 분배할 곳을 찾아야 한다

라우트를 사용하면 이를 시작하기 좋다
웹페이지를 불러오는 시간은 페이지 전환이 어느정도 발생하며
대부분 페이지를 한번에 렌더링하기 때문에 사용자가 페이지를 렌더링 하는 동안
다른 요소와 상호작용하지 않는다

React.lazy를 React Router 라이브러리를 사용해서 애플리케이션에 라우트 기반 코드 분할을 설정하는 예시

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

라우터를 이용하여 페이지 단위로 lazy를 전달하고 있다

profile
떠돌이 생활을 하는. 실업자는 아니지만, 부랑 생활을 하는

0개의 댓글