React19.1 - Component(Profiler, Suspense)

Hunter Joe·2025년 4월 27일

공식문서 : 잘쓰면 UX 최적화를 진행할 수 있을거 같은데 쉽지 않네 (작성일 : 25/4/27)

React에서 Built-in으로 제공하는 제공하는 내장 컴포넌트가 존재하는데 오늘은 4개 중 Profiler,Suspense에 대해 알아보려고 한다.

📌 Profiler

<Profiler>를 통해 React 트리의 렌더링 성능을 프로그래밍 방식으로 측정할 수 있습니다.

Reference

렌더링 성능을 측정하기 위해 컴포넌트 트리를 Profiler로 감싸준다.

<Profiler id="App" onRender={onRender}>
  <App />
</Profiler>

Props

  • id: 성능 측정을 위해 UI컴포넌트를 식별하는 문자열
  • onRender: React가 프로파일링(profiled)된 트리 안의 컴포넌트들이 업데이트 될때마다 호출하는 onRender콜백, 이 콜백은 어떤 것이 렌더링되었고, 런더링에 얼만큼의 시간이 걸렸는지에 대한 정보를 받는다.

    NOTE
    Profiled는 "성능 측정이 적용된" 정도로 생각하면 좋다.
    성능 데이터를 수집하고 있는 컴포넌트 트리를 일컫는다. == "성능 측정을 위해 감싼 트리"

Caveats
Profiling은 추가적인 overhead를 더하기 때문에 기본적으로 배포 환경에서는 비활성화되어있다.
프로덕션 profiling을 사용하려면 프로파일린 기능이 활성화된 특수한 프로덕션 build를 사용해야 한
다.

yarn build --profile
npm run build -- --profile

NOTE

  • overhead : 추가로 드는 비용 or 부하
  • <Profiler>는 가벼운 컴포넌트이지만 사용할 때마다 애플리케이션에 약간의 CPU 및 메모리 오버헤드를 추가하기 때문에 필요할 때만 사용할 것

onRender callback

렌더링된 내용에 대한 정보를 가지고 onRender 콜백을 호출

function onRender(id, phase, actualDuration, baseDuration, startTime, commitTime) {
  // Aggregate or log render timings...
}
  • id : 커밋된 <Profiler>트리의 문자열 id 프로퍼티

  • phase : mount, update,nested-update 중 하나
    트리가 처음으로 마운트되었는지 아니면 props, state, Hooks의 변경으로 다시 렌더링이 되었는지 알 수 있음

  • actualDuration : 업데이트 동안 <Profiler>와 그 하위 트리를 렌더링하는 데 걸린 시간(ms)
    이 값은 서브트리가 memoization을 얼마나 잘 활용하고 있는지를 나타 낸다.
    이상적으로 초기 마운트 이후에는 많은 하위 컴포넌들이 특정 props가 변경될 때만 다시 렌더링되기 때문에, 이 값은 크게 줄어야 한다.

  • baseDuration : 최적화가 전혀 없는 상태에서 <Profiler>의 하위 트리를 전체 다시 렌더링하는데 걸릴 것으로 예상되는 시간(ms)
    트리 안의 각 컴포넌트의 가장 최근 렌더링 시간들을 합산해서 계산
    이 값은 렌더링의 최악의 비용을 추정한 것 (Ex. 초기 마운트 or memoization이 없는 경우)
    actualDuration과 비교해 memoization이 잘 되고 있는지 확인할 수 있다.

  • startTime : 이번 업데이트를 React가 렌더링하기 시작한 시점의 타임스탬프 (Number Type)

  • commitTime : 이번 업데이트를 React가 커밋한 시점의 타임스탬프 (Number Type)
    이 값은 같은 커밋 안에 있는 모든 프로파일러가 공유하므로 필요하다면 서로 그룹화 할 수 있다.

nested update
컴포넌트가 렌더링 중 또 다른 상태 업데이트를 일으켜 추가로 발생한 렌더링

function function MyComponent() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    if (count === 0) {
      setCount(1);  // 렌더링 중에 상태를 바꿔서 "nested update" 발생
    }
  }, [count]);

  return <div>{count}</div>;
}
  • 첫 렌더 → count0
  • useEffect안에서 setCount()를 호출 → 리렌더링 (nested update)

React Developer Tools

<Profiler>컴포넌트는 코드 안에서 직접 성능 데이터를 수집할 수 있다. (프로그래밍적)
이를 React Developer Tools(크롬 익스텐션)은 시각적으로 성능을 분석할 수 있다.
→ Dev tool을 사용해서 유사한 기능들을 사용할 수 있다.




→←↑↓
📌⚠️

📌 Suspense

<Suspense>는 자식 요소(children)를 로드하기 전까지 화면에 대체 UI(Fallback)를 보여준다.

Reference

<Suspense fallback={<Loading />}>
  <SomeComponent /> // children
</Suspense>

Props

  • children: 렌더링하려는 실제 UI, children의 렌더링이 지연되면 Suspense는 fallback을 대신 렌더링
  • fallback: 실제 UI가 로딩되기 전까지 대신 렌더링되는 대체 UI (Ex. 로딩 스피너, 스켈레톤 UI)
    만약 fallback의 렌더링이 지연되면 가장 가까운 부모 Suspense가 활성화

Caveats

  • React는 컴포넌트가 처음 마운트되기 전에 suspened(일시 중지)된 렌더링에 대해 State를 보존 X
    컴포넌트가 로딩되면 일시 중지된 트리를 처음부터 다시 렌더링(순수함수 유지 원칙)

NOTE
렌더링 ? 마운트 ?
렌더링 : 실제 DOM을 그리기 전 어떤 모습으로 그릴지에 대한 청사진 (가상 DOM)
마운트 : 렌더링된 가상 DOM을 가지고 실제 브라우저의 Real DOM을 작업

  • Suspense가 트리의 컨텐츠를 보여주고 있을 때 또 다시 지연되면 startTransitionuseDeferredValue로 인한 업데이트가 아닌 한 fallback이 다시 보여진다.

  • 이미 화면에 표시된 컨텐츠를 다시 숨겨야 할 경우 컨텐츠 트리 안의 layout Effect들을 정리함
    컨텐츠가 다시 표시될 준비가 되면 다시 layout Effect들을 실행
    → DOM 레이아웃을 측정하는 Effect들이 컨텐츠가 숨겨진 상태에서 잘못 동작하지 않도록 보장

NOTE
이 문장은 잘 이해가 안되서 하나씩 좀 뜯어보겠음

  • 이미 화면에 표시된 컨텐츠 : 마운트가 완료된 컴포넌트들
  • layout Effect를 정리 :
    문제는 이렇게 숨김처리를 할때 발생하는데 컴포넌트가 화면에 있을 때는 useLayoutEffect같은 Effect를 통해 DOM의 크기, 스크롤 위치 등을 조절하는 "화면 재조정"작업을 하고 있을 수 있음
    이 때 컨텐츠가 숨겨져서 화면에 없어졌는데도 이런 작업을 계속 진행할 경우 버그가 발생함 → 퍼포먼스 문제
    그러기 때문에 이렇게 layout Effect들은 모두 정리하는 것
  • 다시 숨겨야 할 경우? : 사용자 인터렉션에 의해 기존 UI → 새로운 UI 준비 → 이과정에서 fallback UI를 보여줌
  • Streaming Server Rendering과 Selective Hydration 같은 내부 최적화 기능을 Suspense와 통합하여 제공

Usage

import { Suspense } from 'react';
import Albums from './Albums.js';

export default function ArtistPage({ artist }) {
  return (
    <>
      <h1>{artist.name}</h1>
      <Suspense fallback={<Loading />}>
        <Albums artistId={artist.id} />
      </Suspense>
    </>
  );
}

function Loading() {
  return <h2>🌀 Loading...</h2>;
}

IMPORTANT
Suspense는 특정 상황에서만 활성화
1. Relay, Next.js같은 Suspense가 가능한 프레임워크를 사용한 데이터 페칭
2. lazy를 활용한 로딩 지연 컴포넌트
3. use를 사용한 Promise 값을 읽을 때

useEffect + fetch는 렌더링을 멈추지 않고 로딩 스피너를 따로 관리하는 패턴
Suspense는 렌더링 자체를 중단하고 fallback을 보여주는 패턴

profile
Async FE 취업 준비중.. Await .. (취업완료 대기중) ..

0개의 댓글