왜 지금 React Compiler를 도입 논의해야 하나
최근 React Compiler가 컴파일 단계에서 re-render 최소화를 자동으로 시도하면서, 수동으로 둘러친 memo/useMemo 최적화가 중복 투자일 수 있다는 문제가 떠올랐습니다. 잘못 남긴 메모 최적화는 유지보수 비용과 버그 회귀 리스크를 동시에 키웁니다.
Under the Hood 관점에서 보면 데이터 흐름은 props/state 변화 -> 의존성 추적 -> 컴파일러 수준의 스킵 결정으로 이어집니다. 수동 메모와 컴파일러의 판단이 충돌하면 불필요한 캐시 유지, 구독 누수, 의존성 불일치 같은 비용이 생깁니다.
도입 판단을 미루면 코드베이스는 더 많은 수동 메모 레이어를 쌓게 되고, 나중에 걷어낼 때 회귀 테스트 범위가 기하급수적으로 커집니다. 반대로 성급히 걷어내면 스크롤 리스트, 폼 검증처럼 연산 경계가 중요한 화면에서 프레임 드랍을 감수해야 합니다.
> 안티패턴: “컴파일러가 다 알아서 막아주니 메모 전면 제거”는 위험합니다. 렌더링 경계가 CPU 바운드인 구간, 비싼 파생 계산, 빈번한 컨텍스트 전파는 여전히 제거 기준과 회귀 테스트가 필요합니다.
memo/useMemo가 겹치는 순간: 사례로 보는 충돌 지점
폼 화면에서 onChange로 state가 자주 바뀌는 경우를 보겠습니다. useMemo로 비싼 유효성 계산을 캐싱해두면 좋지만, Compiler가 동일한 의존 그래프를 파악하면 같은 경계를 두 번 세우는 셈입니다.
예를 들어, 부모가 필터 props를 바꿀 때 자식 리스트 아이템을 memo로 감싸면 스크롤은 부드럽지만, Compiler가 prop 동등성 기반으로 re-render를 이미 생략하면 memo가 불필요한 비교 비용과 메모리 참조를 남깁니다.
Under the Hood 관점에서 흐름은 입력 변경 -> 의존성 수집 -> 컴파일 타임 경계 생성입니다. 여기서 수동 memo가 prop 비교 -> 캐시 유지 -> 구독 관리까지 별도 수행하면 경계가 이중화됩니다.
- 계산량이 큰 파생 값(예: 대규모 정렬/그룹핑): 일시 유지가 필요해 useMemo 이점이 남을 수 있음
- 이벤트 핸들러 안정성(참조 동일성 요구 컴포넌트): useCallback 또는 Compiler 최적화 중 하나만 유지
- 리스트 셀 렌더(수천 행): 셀 자체 memo보다 데이터 분할/윈도잉이 1순위, Compiler는 보조 수단
> 안티패턴: “자식은 모두 memo” 접근은 컴파일러 경계와 충돌해 비교 비용만 늘립니다. 먼저 “무엇이 비싼가?”를 측정하고 경계를 하나로 통일하세요.
도입 절차와 체크리스트 요약
컴파일러 적용은 영향 큰 화면부터 범위를 좁혀 시작합니다. 목표는 memo/useMemo 전면 폐지가 아니라 선택적 정리입니다.
- 우선순위: 스크롤 리스트, 대형 폼, 대시보드 등 렌더 빈도·노출 시간 긴 화면
- 경계 수집: memo 컴포넌트, useMemo/useCallback, 의존성 배열 전수 조사
- 비용 태깅: 계산(높음/중간/낮음), 메모리(큼/보통/작음) 라벨
- 제거 후보: 얕은 props 비교로 더 느린 경우, 미사용 캐시, 재계산 비용 낮은 파생 값
- 유지 후보: N^2 정렬, 대규모 필터, 비동기 결과 머지 같은 고비용 파생 값
- 위험 구간: 가상 스크롤 아이템, 드래그·리사이즈 등 고빈도 이벤트 처리
Under the Hood 흐름에 맞춰 회귀 테스트를 설계합니다. 입력 변화→의존성 추적→컴파일러 스킵 결정을 계측 포인트로 둡니다.
- 성능 기준선: TTI, 클릭→반응 지연, 스크롤 FPS
- 메모리/GC: 큰 useMemo 제거 후 힙 스냅샷으로 캐시 해제 확인
- 이벤트 안정성: 콜백 식별자 변화에 민감한 자식(React.memo, deps 훅) 점검
- 스냅샷·E2E: 입력/필터/탭 전환 시 DOM 차이, focus·selection 보존 확인
고비용 연산만 국소 캐시로 묶고 나머지는 단순화합니다. 아래 코드는 구조만 보여줍니다.
// 구조를 보여주기 위한 예시입니다.
function Products({ items, q }) {
const view = useMemo(() => sortAndFilter(items, q), [items, q]);
const onSelect = (id: string) => setSelected(id);
return <List data={view} onSelect={onSelect} />;
}
핸들러는 참조 안정성만 필요하면 inline으로 두고, 불필요한 useCallback을 줄이면 테스트·유지보수 부담이 낮아집니다.
> 안티패턴: “전부 제거 후 문제 시 복구”. 단계적 제거→측정→비교→롤백으로 진행하고, 성능 회귀 3% 이상이면 즉시 중단하세요.
메모 제거·유지 판단의 핵심
가장 흔한 실수는 “일단 memo/useMemo를 유지”하는 선택입니다. 겹치는 경계는 비교·캐시 유지 비용을 늘려 렌더를 줄여도 총비용을 키울 수 있습니다.
- useMemo 재계산이 O(1~n)인데 의존성만 복잡하게 붙잡는 경우
- memo의 얕은 비교가 커스텀 동등성보다 느린 경우(함수/객체 프롭 다수)
- useCallback으로 고정했지만 하위가 레퍼런스 안정성을 요구하지 않는 경우
- 가상 스크롤 아이템을 과도하게 memo해 생성/파괴 비용이 커지는 경우
Compiler는 의존성 그래프 기반 스킵, 수동 메모는 프롭 비교/캐시 반환으로 경로가 달라 중복 시 이중 비용이 납니다.
> 안티패턴: “회귀가 두려워 전부 남긴다.” 디버깅 난도와 의존성 누락 리스크만 커집니다. 최소 한 화면에서 제거 전·후 A/B 계측 후 확장하세요.
선택 기준은 계산 복잡도·상호작용 패턴입니다. 고비용 파생값은 남기고, 이벤트·리스트 경계는 Compiler에 우선권을 둡니다.
- 남김: N log N 이상 정렬/그룹핑, 대용량 필터, 멀티스텝 파싱/검증
- 제거: 단순 매핑/합계, 스타일·클래스 조합, 경량 셀 렌더러
- 조건부: 드래그·스크롤 등 고빈도 이벤트는 하위 의존 여부 확인 후 최소 유지
주의점은 두 가지입니다. 캐시가 크면 GC 압력이 커집니다.
회귀 테스트는 사용자 경로로 설계하고 클릭 지연·스크롤 FPS·메모리 피크를 함께 보세요.
지금 할 일과 확장 순서
첫 화면을 정해 작은 실험으로 시작하세요. 목표는 memo/useMemo의 선택적 정리와 회귀 위험 최소화입니다.
- 대상 선정: 스크롤 리스트·대형 폼 중 1개 화면
- 기준선 수집: TTI, 입력→반응 지연, 스크롤 FPS, 메모리 스냅샷
- 경계 마킹: memo/useMemo/useCallback 위치와 의존성·재계산 비용 태깅
- 단계 제거: 저비용 파생값 → 불필요한 memo(얕은 비교) 순으로 해제
- 회귀 검증: 상호작용 스크립트로 before/after 계측, 스냅샷/비주얼·E2E 병행
- 확장: 성능 이득≥0, 회귀=0이면 유사 패턴 화면으로 범위 확대
Under the Hood 기준으로 계측 포인트를 유지하세요. 입력 변화 -> 의존성 추적 -> 컴파일러 스킵 결정을 로그로 남기면 원인 추적 시간이 줄어듭니다.
> 실무 팁: 리스트 아이템·이벤트 핸들러 경계는 마지막에 다룹니다. 계산형 파생값 정리로 안전 구간을 먼저 확보한 뒤, 레퍼런스 안정성 요구가 낮은 컴포넌트부터 memo를 해제하세요.
성공 판단은 “렌더 감소”가 아니라 총비용입니다. CPU 재계산, 비교·캐시 유지, 메모리 압력, GC 빈도를 함께 보고, 팀이 유지할 수 있는 규칙으로 체크리스트를 문서화하면 확장 시 회귀 리스크를 줄일 수 있습니다.
'프로그래밍' 카테고리의 다른 글
| bfcache 최적화 체크리스트: unload 제거, pagehide/pageshow 전환, DevTools 검사 방법 (0) | 2026.06.29 |
|---|---|
| Tauri 2 자동 업데이트 채널 설계: 서명·릴리스 파이프라인·롤백 전략 가이드 (0) | 2026.06.26 |
| OpenAI Realtime API(WebRTC)로 브라우저 음성 챗봇 구축: 에페메럴 키와 Agents SDK 실전 가이드 (0) | 2026.06.25 |
| ESLint v9 Flat Config 마이그레이션 가이드: typescript-eslint 연동 단계별 설명 (0) | 2026.06.25 |
| React 19 Actions와 useOptimistic으로 폼 제출·낙관적 UI 마이그레이션 체크리스트 (0) | 2026.06.25 |