React 실무 가이드 · Part 6

렌더링 최적화와 React Compiler

성능 문제를 추측이 아니라 측정으로 다루기

작성 기준2026년 7월버전과 지원 현황은 이후 달라질 수 있으니 공식 문서를 함께 확인하세요.

이 파트에서 다루는 내용

렌더링 비용메모이제이션 도구React Compiler최적화 순서
01

리렌더링 자체는 문제가 아닙니다

React 컴포넌트가 다시 실행되는 것은 정상 동작입니다. 대부분의 컴포넌트는 다시 실행돼도 사람이 느낄 만한 지연이 없습니다.

문제가 되는 경우는 정해져 있습니다. 무거운 계산이 반복되거나, 큰 목록 전체가 다시 그려지거나, 렌더링이 연쇄적으로 번질 때입니다.

그래서 최적화의 첫 단계는 코드를 고치는 것이 아니라 원인을 찾는 것입니다. 어디가 느린지 모른 채 메모이제이션을 붙이면 코드만 복잡해집니다.

React DevTools Profiler
측정 도구

어떤 컴포넌트가 얼마나 자주, 얼마나 오래 렌더링됐는지 보여 줍니다. 최적화는 여기서 시작합니다.

느려지는 전형적인 경우
패턴
  • 수백 개 이상의 목록을 매번 다시 그리는 경우
  • 입력할 때마다 무거운 계산이 도는 경우
  • 상위 컴포넌트 상태 변경이 화면 전체로 번지는 경우
먼저 볼 것
구조

메모이제이션보다 상태 위치를 먼저 봅니다. 상태를 필요한 곳 가까이 내리면 렌더링 범위 자체가 줄어듭니다.

02

메모이제이션 도구는 세 가지입니다

React가 제공하는 수동 최적화 도구는 역할이 조금씩 다릅니다. 무엇을 기억하는지 기준으로 구분하면 헷갈리지 않습니다.

memo
컴포넌트

props가 바뀌지 않으면 컴포넌트 재실행을 건너뜁니다. 부모가 자주 렌더링되는데 자식은 바뀔 이유가 없을 때 씁니다.

useMemo
계산 결과

의존성이 바뀔 때만 값을 다시 계산합니다. 정렬이나 집계처럼 비용이 큰 계산에 씁니다.

useCallback
함수

함수를 기억해 참조가 매번 바뀌지 않게 합니다. memo로 감싼 자식에게 함수를 넘길 때 의미가 있습니다.

함께 써야 효과가 납니다
주의

memo로 감싼 자식에게 매번 새 객체나 새 함수를 넘기면 memo는 무력화됩니다. 한쪽만 적용하면 효과가 없습니다.

memo가 무력화되는 경우jsx
const OrderRow = memo(function OrderRow({ order, onSelect }) {
  return <li onClick={() => onSelect(order.id)}>{order.name}</li>;
});

function OrderList({ orders }) {
  // 매 렌더링마다 새 함수가 만들어져 memo가 소용없습니다
  return orders.map((order) => (
    <OrderRow key={order.id} order={order} onSelect={(id) => select(id)} />
  ));
}

// 함수 참조를 고정해야 memo가 동작합니다
function OrderList({ orders }) {
  const handleSelect = useCallback((id) => select(id), []);
  return orders.map((order) => (
    <OrderRow key={order.id} order={order} onSelect={handleSelect} />
  ));
}

이런 구조를 손으로 맞추는 일이 번거롭기 때문에 컴파일러가 등장했습니다. 다음 섹션에서 다룹니다.

03

React Compiler가 이 작업을 대신합니다

React Compiler는 2025년 10월 v1.0으로 정식 출시됐습니다. 빌드 시점에 컴포넌트를 분석해 필요한 메모이제이션을 자동으로 넣어 줍니다.

즉 위 예제처럼 useCallback과 memo를 손으로 맞추던 작업 상당 부분을 컴파일러가 처리합니다. 개발자는 로직에 집중하고 최적화는 빌드가 담당하는 구조입니다.

지원 버전
React 17·18·19

React 19는 별도 설정 없이 동작합니다. 17과 18은 런타임 패키지를 설치하고 대상 버전을 지정해야 합니다.

점진 도입
적용 범위 조절
  • 특정 디렉터리에만 먼저 적용
  • 컴포넌트에 지시어를 붙여 선택적으로 적용
  • 런타임 플래그로 켜고 끄며 비교
기존 코드의 메모이제이션
판단

컴파일러는 손으로 넣은 메모이제이션을 임의로 지우지 않습니다. 기존 코드는 그대로 두거나, 충분히 검증한 뒤에만 정리하는 편이 안전합니다.

전제 조건
중요

컴파일러는 React의 규칙을 지킨 코드를 전제로 최적화합니다. 렌더링 중 값을 직접 수정하는 코드가 있으면 적용 과정에서 문제가 드러납니다.

실무 판단

새로 시작하는 프로젝트라면 컴파일러를 켜고 수동 메모이제이션 없이 작성하는 방향이 자연스럽습니다. 운영 중인 프로젝트라면 일부 디렉터리에 먼저 적용해 동작을 확인한 뒤 범위를 넓히는 편이 안전합니다. 켜자마자 기존 useMemo를 일괄 삭제하는 것은 권하지 않습니다.

04

최적화는 순서가 있습니다

  • 1. 정말 느린지 확인합니다. 체감 문제가 없으면 최적화하지 않습니다.
  • 2. Profiler로 어느 컴포넌트가 원인인지 찾습니다.
  • 3. 상태 위치와 컴포넌트 구조를 먼저 조정합니다. 범위를 줄이는 것이 가장 효과가 큽니다.
  • 4. 목록이 크면 화면에 보이는 부분만 그리는 방식을 검토합니다.
  • 5. 그래도 남는 비용에 메모이제이션이나 컴파일러를 적용합니다.
  • 6. 적용 전후를 다시 측정해 효과를 확인합니다.
구조로 푸는 예
가장 효과적

입력값 상태를 페이지 최상단에 두면 입력할 때마다 화면 전체가 다시 그려집니다. 입력 영역을 별도 컴포넌트로 분리하면 그 안에서만 렌더링이 일어납니다.

과한 최적화의 비용
주의

메모이제이션도 공짜가 아닙니다. 비교 비용과 메모리를 쓰고 코드 가독성이 떨어집니다. 효과가 확인되지 않은 최적화는 손해입니다.

렌더링 밖의 원인
확인

느린 이유가 렌더링이 아니라 큰 이미지, 과도한 번들 크기, 느린 API인 경우도 많습니다. 원인을 먼저 확인합니다. Part 13에서 다룹니다.

버전

버전별 참고

본문은 React 19 기준입니다. 18과 달라지는 부분은 아래에 정리합니다.

  • React Compiler는 v1.0이 2025년 10월 7일 정식 출시됐고 React 17, 18, 19를 지원합니다.
  • React 19에서는 기본 설정으로 바로 쓸 수 있습니다. 17과 18은 react-compiler-runtime 패키지 설치와 대상 버전 지정이 필요합니다.
  • 컴파일러 도입 여부와 무관하게 이 파트의 진단 순서(측정 후 구조 조정)는 동일하게 적용됩니다.
체크

이 파트 완료 기준