React 심화 (3) - Custom Hooks, React.lazy, React.Suspense

선정·2022년 7월 29일
0

Today I Learned

  • Custom Hooks
  • 코드 분할 (Code Spliting)
  • React.lazy
  • React.Suspense

Custom Hooks

상태 관련 로직을 컴포넌트 간에 재사용할 때 커스텀 훅을 활용할 수 있다.
커스텀 훅은 기능이라기보다는 컨벤션(convention)에 가깝다. 이름이 ”use“로 시작하고, 안에서 다른 Hook을 호출한다면 그 함수를 custom Hook이라고 부를 수 있다.

친구의 접속 상태를 구독하기 위해서 useState와 useEffect Hook을 사용한 FriendStatus 컴포넌트 예시를 보자. 먼저, 이 로직을 useFriendStatus라는 custom Hook으로 뽑아낸다.


hooks/useFriendStatus.js

import React, { useState, useEffect } from 'react';

function useFriendStatus(friendID) {
  const [isOnline, setIsOnline] = useState(null);

  function handleStatusChange(status) {
    setIsOnline(status.isOnline);
  }

  useEffect(() => {
    ChatAPI.subscribeToFriendStatus(friendID, handleStatusChange);
    return () => {
      ChatAPI.unsubscribeFromFriendStatus(friendID, handleStatusChange);
    };
  });

  return isOnline;
}

useFriendStatus 커스텀 훅은 friendID를 인자로 받아서 친구의 접속 상태를 판별하는 isOnline state를 반환한다. 해당 커스텀 훅을 컴포넌트에서 호출해서 사용해보자.


FriendStatus.js

import { useFriendStatus } from "./hooks/useFriendStatus.js";

function FriendStatus(props) {
  const isOnline = useFriendStatus(props.friend.id);

  if (isOnline === null) {
    return 'Loading...';
  }
  return isOnline ? 'Online' : 'Offline';
}

FriendListItem.js

import { useFriendStatus } from "./hooks/useFriendStatus.js";

function FriendListItem(props) {
  const isOnline = useFriendStatus(props.friend.id);

  return (
    <li style={{ color: isOnline ? 'green' : 'black' }}>
      {props.friend.name}
    </li>
  );
}

FriendStatus 컴포넌트와 FriendListItem 컴포넌트에 useFriendStatus를 각각 호출해 사용했다.
각 컴포넌트의 state는 완전히 독립적이다. 이렇게 hook은 state 그 자체가 아니라, 상태 관련 로직을 재사용할 수 있도록 하는 것이다.



코드 분할 (Code Spliting)

JavaScript의 코드 양이 많아지고 무거워지면서, 번들링하게 되면 특정 지점에서 코드를 해석하고 실행하는 정도가 느려지는 현상이 발생했다.
그래서 어느 페이지에서 코드 해석 및 실행이 느려졌는지 파악해 번들을 나눈 뒤 필요한 코드만 불러오고 나중에 필요한 코드는 나중에 불러오게 하는, 코드 분할 (Code Spliting)이 도입되었다. 이를 통하여, 페이지의 로딩 속도를 개선 할 수 있다.

코드 분할 (Code Spliting)은 런타임 시 여러 번들을 동적으로 만들고 불러오는 것으로, Webpack 같은 번들러가 지원하는 기능이다.

번들 분할 혹은 줄이는 법

번들링 되는 파일에 포함되는 서드파티 라이브러리를 불러올 때, 전부 불러와서 사용하는 것보다 따로 따로 불러와서 사용하면 번들링 시에 많은 공간을 차지하지 않을 수 있다.

// 라이브러리 전체를 불러와서 꺼내쓰기 => 공간 차지 많이 함
import _ from 'lodash';

_.find([]);

// 라이브러리 내 필요한 메소드만 불러와서 쓰기 => 차지하는 공간이 작아짐
import find from 'lodash';

find([])


React는 SPA이기에 사용하지 않는 모든 컴포넌트까지 한 번에 불러와 첫 화면이 렌더링 될 때까지 시간이 오래 걸린다. 그래서 지금 접근하지 않는 컴포넌트는 나중에 불러오기 위해 코드 분할 개념을 도입했다. 리액트에서 코드 분할하는 방법은 Dynamic Import(동적 불러오기)를 사용하는 것이다.


Static Import(정적 불러오기)

코드 파일의 가장 최상위에 import()를 사용해 사용하고자 하는 라이브러리 및 파일을 불러오는 방법이다.
번들링 시 코드 구조를 분석해 모듈을 한 데 모으고 사용하지 않는 모듈은 제거하는 등의 작업을 하는데, 코드 구조가 간단하고 고정돼 있을 때에만 이 작업이 가능했으므로, 블록문 안에서는 위치할 수 없는 제약이 있다.

// 파일의 최상위에서 import
import { add } from './math';

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

Dynamic Import(동적 불러오기)

불러온 라이브러리나 파일을 특정한 경우에만 가져오도록 할 수 있다.
예시 코드에서 Dynamic Importthen 함수를 사용해 필요한 코드만 가져온다. 가져온 코드에 대한 모든 호출은 해당 함수 내부에 있어야 한다. 이 방식을 사용하면 번들링 시 분할된 코드(청크)를 지연 로딩시키거나 요청 시에 로딩할 수 있다.

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


React.lazy

React.lazy 함수를 사용하면 Dynamic Import를 사용해 컴포넌트를 렌더링할 수 있다. 이 방법으로 초기 렌더링 지연시간을 어느정도 줄일 수 있게 된다.


MyComponent.js

// React.lazy를 사용하는 Dynamic Import
const OtherComponent = React.lazy(() => import('./OtherComponent'));

MyComponent가 처음 렌더링 될 때 OtherComponent를 포함한 번들을 자동으로 불러온다.

React.lazy동적 import()를 호출하는 함수를 인자로 가진다. 이 함수는 React 컴포넌트를 default export로 가진 모듈 객체가 이행되는 Promise를 반환해야 한다.

lazy 컴포넌트는 Suspense 컴포넌트 하위에서 렌더링되어야 한다.



React.Suspense

React.Suspense는 lazy 컴포넌트가 로드되길 기다리는 동안 로딩 화면과 같은 예비 컨텐츠를 보여주고 로드가 완료되면 렌더링이 준비된 컴포넌트를 보여주는 기능이다.

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

fallback prop은 컴포넌트가 로드될 때까지 기다리는 동안 렌더링하려는 React 엘리먼트를 받아들인다. Suspense 컴포넌트는 lazy 컴포넌트를 감싼다. 하나의 Suspense 컴포넌트로 여러 lazy 컴포넌트를 감쌀 수도 있다.


Route-based code splitting

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

아래와 같이 적용 했을 때, 초기 렌더링 시간이 줄어드는 장점이 있으나 페이지를 이동하는 과정마다 로딩 화면이 보여지기 때문에 서비스에 따라서 적용 여부를 결정해야 한다.

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

0개의 댓글