728x90
728x90

코드 분할(Code Splitting) : useTransition 훅, Suspense 컴포넌트, lazy 함수

들어가며

  • 리액트(React.js)에서 코드 분할(Code Splitting)의 장점과 함께 사용되는 @useTransition@ 훅과 @Suspense@ 컴포넌트, @lazy@ 함수에 대해 정리해본다.

 

코드 분할(Code Splitting)

개념

  • 리액트 애플리케이션에서 코드의 일부분을 나눠서 필요한 시점에 로드하는 방식

 

장점

  • 애플리케이션을 더 작은 청크(Chunk)로 나눔으로써 초기 로드 시 필요한 자바스크립트 크기를 줄인다.
    • 이를 통해 첫 화면 렌더링 속도가 빨라지며, 느린 네트워크 환경에서도 빠르게 응답할 수 있다.
  • 사용자가 필요로 하지 않는 코드는 나중에 로드되며, 상호작용할 때만 필요한 코드가 동적으로 로드되어 매끄러운 사용자 경험을 제공한다.
  • 페이지 간 트래픽 비율에 따라 중요한 리소스를 우선적으로 로드하고, 덜 중요한 리소스는 나중에 로드할 수 있어 성능을 최적화(Optimization)할 수 있다.

 

useTransition 훅

개념

  • React 18에서 도입되었으며, 비동기 상태 업데이트를 처리할 때 UI의 응답성을 개선하는 데 사용된다.
  • 주로 UI 작업의 우선순위를 설정해 중요한 작업은 즉시 처리하고, 덜 중요한 작업은 비동기적으로 처리하는데 사용된다.
  • 복잡한 작업이나 데이터 로드 시 UI 성능을 유지하는 데 유용하다.

 

사용 방법

  • @isPending@은 비동기 작업진행 중인지를 나타낸다.
    • 전환이 완료되면 @false@가 된다.
  • @startTransition@은 우선순위가 낮은 작업을 비동기로 처리하여 UI 응답성을 유지한다.
import { useTransition } from 'react';

const [isPending, startTransition] = useTransition();

const handleClick = () => {
  startTransition(() => {
    // 우선순위가 낮은 상태 업데이트
    setState(someExpensiveOperation());
  });
};

 

사용 예

  • 아래와 같이 @useTransition@ 훅을 이용하여 UI가 버벅거리지 않고 필터링 작업이 완료될 때까지 로딩 메시지(@isPending@에 따른 상태)로 사용자에게 피드백을 제공할 수 있다.
import { useState, useTransition } from "react";

// 랜덤 아이템 생성 함수
function generateItems() {
  return Array.from({ length: 5000 }, (_, i) => `Item ${i + 1}`);
}

function FilterableList() {
  const [query, setQuery] = useState("");                   // 검색어 상태
  const [list, setList] = useState(generateItems());        // 검색어 리스트
  const [isPending, startTransition] = useTransition();

  // 검색어 변경 시 필터링 작업을 처리
  const handleInputChange = (e) => {
    const inputValue = e.target.value;
    setQuery(inputValue);  // 검색어 상태 즉시 업데이트

    // '필터링' 작업은 우선순위가 낮은 작업으로 처리하기
    startTransition(() => {
      const filteredList = generateItems().filter((item) =>
        item.toLowerCase().includes(inputValue.toLowerCase())
      );
      setList(filteredList);  // (필터링 작업 후) 검색어 리스트 업데이트
    });
  };

  return (
    <div>
      <input
        type="text"
        value={query}
        onChange={handleInputChange}
        placeholder="Search..."
      />
      {isPending ? <p>Updating List...</p> : null}   {/* isPending으로 로딩 상태 표시하기 */}
      <ul>
        {list.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

export default FilterableList;

 

Suspense 컴포넌트

개념

  • 코드 분할이나 비동기 로딩 시, 컴포넌트가 로드될 때까지의 대기 상태를 처리하는 리액트 컴포넌트
  • 주로 동적 임포트나 비동기 렌더링이 필요한 상황에서 로딩 중 UI를 지정할 수 있게 해준다.

 

사용 방법

  • @fallback@ 속성에 로드 중에 표시할 컴포넌트를 지정한다.
    • 로딩 중 스피너(Spinner)나 텍스트 등을 지정할 수 있다.
import { Suspense } from 'react';

<Suspense fallback={<div>Loading...</div>}>
  <LazyLoadedComponent />
</Suspense>

 

lazy 함수

개념

  • 동적으로 컴포넌트를 로드하는 데 사용된다.
  • 코드 분할을 통해 애플리케이션의 특정 컴포넌트를 필요한 시점에 로드할 수 있게 해준다.
  • @Suspense@ 컴포넌트와 함께 사용된다.
  • @lazy@를 사용하면, 해당 컴포넌트가 필요할 때만 동적으로 @import@ 된다.
    • 이를 통해 처음부터 모든 코드를 불러오지 않고, 사용자가 특정 동작을 했을 때만 관련 코드를 로드하는 방식으로 애플리케이션의 성능을 크게 향상시킬 수 있다.

 

사용 방법

const LazyLoadedComponent = lazy(() => import('./LazyComponent'));

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

 

정리

  • 코드 분할(Code Splitting)
    • 성능 최적화 및 사용자 경험 개선에 도움이 되는 기법
    • 초기 로드 시 모든 코드를 불러오는 대신, 필요한 시점에 코드를 동적으로 로드하여 성능을 향상시킨다.
  • @useTransition@ 훅
    • 우선 순위가 낮은 작업을 비동기 처리한다.
    • 사용자 인터페이스가 버벅거리는 것을 방지한다.
  • @Suspense@ 컴포넌트
    • 비동기적으로 로드되는 컴포넌트가 준비될 때까지 로딩 상태를 처리해준다.
    • 로딩 중에 표시할 UI를 제공한다.
  • @lazy@ 함수
    • 동적으로 컴포넌트를 로드하여 불필요한 코드가 한 번에 로드되는 것을 방지한다.
    • 코드 분할을 쉽게 할 수 있도록 도와준다.

 

종합 사용 예제
import { useState, useTransition, lazy, Suspense } from 'react';

const SlowComponent = lazy(() => import('./components/SlowComponent'));

const UseTransitionSuspenseExample = () => {
  const [text, setText] = useState('');
  const [items, setItems] = useState([]);
  const [show, setShow] = useState(false);

  const [isPending, startTransition] = useTransition();

  const handleChange = (e) => {
    setText(e.target.value);

    startTransition(() => {
      const newItems = Array.from({ length: 200 }, (_, index) => {
        return (
          <div key={index}>
            <img
              src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSlGmKtrnxElpqw3AExKXPWWBulcwjlvDJa1Q&s"
              alt=""
            />
          </div>
        );
      });

      setItems(newItems);
    });
  };

  return (
    <div className="section">
      <h1>useTransition and Suspense Example</h1>
      <Suspense fallback={<h4>Loading...</h4>}>
        <section>
          <form className="form">
            <input
              type="text"
              className="form-input"
              value={text}
              onChange={handleChange}
            />
          </form>
          <h4>Items Below</h4>
          {/* 다 불러와지기 전에 Loading... 표시하기 */}
          {isPending ? (
            'Loading...'
          ) : (
            <div
              style={{
                display: 'grid',
                gridTemplateColumns: '1fr 1fr 1fr',
                marginTop: '2rem',
              }}
            >
              {items}
            </div>
          )}
          <button onClick={() => setShow(!show)} className="btn">
            toggle
          </button>
          {show && <SlowComponent />}
        </section>
      </Suspense>
    </div>
  );
};

export default UseTransitionSuspenseExample;

 

 

 

참고 사이트

 

useTransition – React

The library for web and native user interfaces

ko.react.dev

 

<Suspense> – React

The library for web and native user interfaces

ko.react.dev

 

lazy – React

The library for web and native user interfaces

ko.react.dev

 

728x90
728x90