React 19 와 React Compiler
React 19 와 React Compiler
React 19 는 React 의 큰 변곡점 중 하나로 자주 거론됩니다. 그 가운데 React Compiler 는 그동안 손으로 적던 메모를 일부 자동화합니다.
1. React 에 대한 이야기
React 는 2011 년 Facebook 의 Jordan Walke 가 사내 도구 (News Feed) 를 위해 만들었고, 2013 년 5 월 JSConf US 에서 오픈소스로 공개됐습니다. 라이선스는 MIT (2017 년 일시적으로 BSD+Patents 였다가 MIT 로 환원).
| 버전 | 시기 | 사건 |
|---|---|---|
| 0.14 | 2015 | react-dom 분리. |
| 16 | 2017-09 | Fiber 재작성. error boundary · fragments · portals. |
| 16.8 | 2019-02 | Hooks 정식. useState · useEffect. |
| 17 | 2020-10 | "no new features" 릴리스. 점진적 업그레이드 경로. |
| 18 | 2022-03 | Concurrent renderer · automatic batching · useTransition. |
| 19 | 2024-12 GA | Server Components 안정 · use · Actions · ref-as-prop · <form action> 통합. |
19 와 함께 (별도 패키지로) React Compiler 가 RC 단계로 공개됐습니다.
2. React 19 핵심
- Actions —
<form action={fn}>또는useActionState·useFormStatus로 폼 제출의 pending/error/optimistic 상태를 하나의 API 로 묶습니다. use(thenable)— 컴포넌트에서 promise 또는 context 를 직접 unwrap. Suspense 와 결합합니다.- ref as prop —
forwardRef가 사실상 필요 없어집니다.function X({ ref }) { ... }가 가능합니다. useOptimistic— 낙관적 UI 갱신을 위한 훅.- 메타데이터 통합 —
<title>·<meta>·<link rel="stylesheet">를 컴포넌트 트리 어디서든 선언하면 React 가 head 로 끌어올립니다.
3. React Compiler 의 자동화
전통적인 React 에서 재렌더 최적화는 손으로 합니다.
const value = useMemo(() => heavy(a, b), [a, b])
const onClick = useCallback(() => setX(x + 1), [x])
const Memo = memo(Child)
Compiler 는 빌드 타임에 컴포넌트 함수를 분석해 같은 입력이면 같은 결과를 재사용하도록 메모이제이션을 자동 삽입합니다 (공식 문서 표현으로 "auto-memoization"). 결과적으로 useMemo · useCallback · memo 를 수동으로 적을 일이 줄어듭니다. 출력 코드는 react-compiler-runtime 의 c() 캐시 슬롯을 사용합니다.
전제 조건이 있습니다. React 의 규칙을 지키는 코드여야 합니다.
- 렌더 함수는 순수 (같은 props/state 면 같은 출력).
- 렌더 중 props·state 를 변경하지 않습니다.
- Hooks 의 규칙 (최상단 호출, 조건부 호출 금지) 을 지킵니다.
규칙을 어기면 Compiler 는 그 컴포넌트를 건너뜁니다 (opt-out). ESLint 의 eslint-plugin-react-compiler 가 위반을 미리 잡아냅니다.
4. Compiler 가 해결하지 못하는 자리
자동 메모는 참조 안정성 (referential equality) 을 컴포넌트 경계 안에서 다룹니다. 다음은 여전히 사람이 봐야 합니다.
- 외부 mutation 의존 —
useEffect의 deps 에 변하는 객체가 들어오는 패턴. Compiler 는 effect 본문을 감싸지 않습니다. - Observer · subscription —
IntersectionObserver·ResizeObserver의 콜백을 안정 참조로 유지해야 effect cleanup 이 올바릅니다.useCallback또는useEffectEvent가 필요할 수 있습니다. - AbortController 패턴 — effect 안에서 만든 controller 의 abort 호출은 cleanup 에서 손으로 합니다.
- 스프레드 deps —
useEffect(fn, [...deps])처럼 동적 길이 deps 는 Compiler 가 보증할 수 없습니다. - 외부 라이브러리 콜백 — 라이브러리가 동일 참조를 요구할 때 (예:
addEventListener/removeEventListener짝맞춤) 는 명시적 보존이 안전합니다.
즉 Compiler 는 "수동 메모를 새로 작성할 필요" 를 줄이지만 모든 경우의 수동 메모를 대체하지 않습니다.
5. UI 라이브러리 비교
| 라이브러리 | 첫 릴리스 | 특징 |
|---|---|---|
| React | 2013 | VDOM · Hooks · Server Components. |
| Vue | 2014, Evan You | reactive proxy · 단일 파일 컴포넌트. |
| Svelte | 2016, Rich Harris | 컴파일러가 런타임을 거의 없앤다. 5 에서 runes 도입 (2024). |
| Solid | 2021, Ryan Carniato | fine-grained reactivity. JSX 사용. |
| Preact | 2015 | React 호환의 작은 구현. 3KB 대. |
| Qwik | 2022, Misko Hevery | resumability — hydration 을 미루는 모델. |
Compiler 는 "수동 메모" 라는 React 특유의 부담을 줄이려는 시도입니다. 다른 라이브러리는 같은 자리에 다른 답을 가집니다 (Vue 의 reactive · Svelte 의 컴파일 타임 추적 · Solid 의 signal).
6. 설치와 설정
pnpm add react@19 react-dom@19
pnpm add -D babel-plugin-react-compiler eslint-plugin-react-compiler
// babel.config.js
export default {
plugins: [
["babel-plugin-react-compiler", { /* options */ }]
]
}
Next.js 는 next.config.js 의 experimental.reactCompiler 옵션, Vite 는 vite-plugin-react 의 babel.plugins 항목으로 배선합니다.
7. 19 의 폼 액션
import { useActionState } from "react"
async function submit(prev: State, form: FormData) {
const r = await fetch("/api", { method: "POST", body: form })
return await r.json()
}
export function NewItem() {
const [state, action, pending] = useActionState(submit, { error: null })
return (
<form action={action}>
<input name="title" />
<button disabled={pending}>저장</button>
{state.error && <p>{state.error}</p>}
</form>
)
}
8. Compiler 가 처리하는 모양
// before: 손으로 메모
const items = useMemo(() => list.filter(p), [list, p])
// after: 그냥 표현식. Compiler 가 캐시 슬롯에 넣음.
const items = list.filter(p)
9. 자주 걸리는 자리
점진 도입 — Compiler 는 파일·디렉토리 단위로 켤 수 있습니다. 큰 코드베이스에서 한 번에 켜는 것보다 단계 도입이 안전합니다.
DevTools 표시 — Compiler 가 적용된 컴포넌트는 React DevTools 에서 "Memo ✨" 같은 표시가 붙습니다. 안 붙으면 규칙 위반으로 건너뛴 것일 수 있습니다.
Strict Mode 의 이중 호출 — React 18+ Strict Mode 는 effect 를 의도적으로 두 번 실행합니다. 부작용을 가진 코드는 Compiler 와 무관하게 멱등성을 가져야 합니다.
자동 메모는 0 비용이 아닙니다 — 캐시 비교 비용 자체가 추가됩니다. 아주 작은 컴포넌트는 오히려 손해라는 경우도 보고됩니다. 일반적인 권장은 "Compiler 켜고 측정 후 판단".
수동 메모와의 공존 — 기존 useMemo · useCallback 을 강제로 제거할 필요는 없습니다. Compiler 는 그 위에 자기 캐시를 더하는 것이 아니라 그대로 둡니다.
하고픈 말
React Compiler 는 매뉴얼 메모의 부담을 크게 줄여 줍니다. 다만 모든 자리를 대체하지는 않습니다. 규칙을 지키는 코드 + 예외 케이스 (Observer · 외부 콜백 등) 의 수동 메모 유지가 가장 균형잡힌 흐름입니다.
Next
- nextjs-app-router
- state-philosophy
React 공식 사이트 · React Compiler 문서 · React 19 릴리스 노트 · Rules of React · eslint-plugin-react-compiler · Vue.js · SolidJS 를 참고합니다.