React Compiler 란?
1. 배경: "모든 것을 메모이제이션 해야 할까?"
기존의 딜레마
오랫동안 리액트 커뮤니티에서는 **"어디서, 얼마나 자주 메모이제이션을 해야 하는가?"**에 대한 논쟁이 있었습니다.
- 이상: 불필요한 리렌더링 방지를 위해 모든 것을 메모이제이션 하고 싶음.
- 현실: 메모이제이션 자체도 비용임. (Props 비교 비용 vs 리렌더링 비용)
- 문제: 개발자가 수동으로 의존성 배열(dependency array)을 관리해야 하며, 이는 코드 복잡도를 높이고 버그를 유발함.
해결책: React Compiler
리액트 컴파일러는 "가능한 모든 것을 자동으로 메모이제이션" 함으로써 이 논쟁을 종결시킵니다. 개발자는 더 이상 성능 최적화를 위해 비즈니스 로직을 복잡하게 만들 필요가 없습니다.
2. 핵심 원리: 어떻게 동작하는가? (How it works)
리액트 컴파일러는 런타임에 실행되던 최적화 로직을 컴파일 단계로 가져와 useMemoCache라는 저수준 훅으로 변환합니다.
2.1 동작 메커니즘 (코드 비교)
BEFORE (수동 최적화 또는 최적화 없음)
const Movies = ({ movie, person }) => {
// 매 렌더링마다 실행되거나, 개발자가 수동으로 useMemo를 작성해야 함
const favoriteMovies = findFavoriteMovies(movie, person)
return <MoviesList items={favoriteMovies} />
}AFTER (React Compiler 변환 결과 - 의사 코드)
컴파일러는 의존성 변화를 감지하는 if 문과 캐싱 로직을 자동으로 주입합니다.
import { c as _c } from 'react/compiler-runtime'
const Movies = ({ movie, person }) => {
const $ = _c(3) // 1. 고정 크기의 캐시 생성 (영화, 인물, 결과값 저장용)
let temp
// 2. 의존성(movie, person)이 변경되었는지 캐시와 비교
if ($[0] !== movie || $[1] !== person) {
// 3. 변경되었다면 재계산 후 캐시 업데이트
temp = findFavoriteMovies(movie, person)
$[0] = movie
$[1] = person
$[2] = temp
} else {
// 4. 변경되지 않았다면 캐시된 값 재사용
temp = $[2]
}
const favoriteMovies = temp
return <MoviesList items={favoriteMovies} />
}2.2 컴파일러의 전제 조건 (3가지 가정)
컴파일러가 안전하게 코드를 변환하기 위해 다음 3가지 조건이 필요합니다. (대부분의 TS/Linter 환경에서 이미 충족됨)
- 유효한 자바스크립트 코드일 것.
- Null Safe: 값/속성에 접근 전 정의 여부를 확인할 것.
- React 규칙 준수: 훅은 컴포넌트 최상위에서만 호출 등.
- 규칙을 어기면? 컴파일러가 이를 감지하고 해당 컴포넌트의 최적화를 안전하게 건너뜁니다(Safe Skip).
3. Playground 분석: 인간보다 똑똑한 컴파일러
리액트 컴파일러 플레이그라운드에서 확인된 결과는 useMemo보다 더 효율적인 최적화 기법을 보여줍니다.
3.1 상수 폴딩 (Constant Folding) & JSX 인라인
컴파일러는 정적인 값을 런타임 계산 없이 결과물에 바로 박아넣습니다.
원본 코드:
const text = 'some text'
const text2 = text + text // 단순 문자열 결합
return <div>{text2}</div>컴파일러 출력:
// 런타임에 'some text' + 'some text'를 연산하지 않음.
// 이미 합쳐진 결과를 JSX에 바로 삽입함.
if ($[0] === Symbol.for('react.memo_cache_sentinel')) {
t0 = <div>{'some textsome text'}</div>
$[0] = t0
}4. 결론 및 실무 가이드 (Action Item)
Q. 이제 useMemo, useCallback은 영원히 안녕인가요?
A. 네, 대부분의 경우 그렇습니다.
- 새로운 코드: 컴파일러를 도입했다면 수동 메모이제이션 훅을 작성할 필요가 없습니다. 코드가 훨씬 간결해집니다.
- 기존 코드: 이미 작성된
useMemo를 지울 필요는 없습니다. 컴파일러는 기존 수동 최적화 코드도 이해하고 올바르게 컴파일합니다.
Q. 수동 최적화가 필요한 예외 케이스는?
A. 애플리케이션 전역 레벨의 최적화
리액트 컴파일러는 주로 컴포넌트 및 훅 내부의 최적화에 집중합니다.
- 여러 컴포넌트에서 공유하며 사용하는 매우 무거운 계산 로직.
- 리액트 트리 외부에서 관리되어야 하는 데이터.
- 이러한 경우에는 리액트 외부(또는 전역 상태 관리)에서 별도의 메모이제이션 전략을 가져가는 것이 합리적입니다.
Final Thought: 리액트 컴파일러는 개발자가 **"성능 최적화(Memozation)"**라는 기술적 부채에서 벗어나 "비즈니스 로직" 그 자체에 집중할 수 있게 해주는 강력한 도구입니다.