React Compiler가 나온 뒤 useMemo와 useCallback이 필요없어진 이유
2026년 2월 5일
useMemo와 useCallback은 불필요한 리렌더링을 막기 위해 썼다. 값이나 함수를 캐싱해두고, 의존성이 바뀌지 않으면 이전 결과를 재사용하는 방식이다. React 19에서 React Compiler가 정식 도입되면서 이 작업을 직접 할 필요가 없어졌다.
기존 방식의 문제
useMemo와 useCallback은 성능 최적화 도구지만, 잘못 쓰면 오히려 코드만 복잡해졌다.
// 최적화한다고 썼지만 의존성 배열 관리가 복잡해진다
const filtered = useMemo(() => {
return items.filter((item) => item.active);
}, [items]);
const handleClick = useCallback(() => {
onSelect(id);
}, [onSelect, id]);
의존성 배열에 뭘 넣어야 하는지 헷갈리고, 빠뜨리면 stale 클로저 버그가 생긴다. 실제로 성능 병목이 없는데 습관적으로 감싸는 경우도 많았다.
React Compiler가 하는 일
React Compiler는 빌드 타임에 컴포넌트 코드를 분석해서 자동으로 메모이제이션을 적용한다. 개발자가 직접 useMemo, useCallback을 쓰지 않아도 컴파일러가 어떤 값과 함수를 캐싱해야 하는지 판단한다.
// 컴파일러가 알아서 최적화한다
function List({ items, onSelect, id }) {
const filtered = items.filter((item) => item.active);
return (
<ul>
{filtered.map((item) => (
<li key={item.id} onClick={() => onSelect(id)}>
{item.name}
</li>
))}
</ul>
);
}
위 코드에서 filtered와 onClick 핸들러는 컴파일러가 의존성을 추적해서 필요할 때만 재계산되도록 변환한다.
그래도 써야 하는 경우?
React Compiler가 모든 상황을 커버하지는 않는다.
Rules of React를 위반한 코드는 컴파일러가 최적화를 건너뛴다. 컴포넌트 외부 상태를 직접 변경하거나, 렌더링 중 사이드이펙트가 있는 코드가 해당된다. 이런 경우엔 수동 메모이제이션이 여전히 필요하다.
서드파티 라이브러리와의 연동에서 참조 동일성이 중요한 경우도 있다. 라이브러리 내부에서 === 비교로 리렌더링을 제어한다면 직접 useCallback으로 안정적인 참조를 보장해야 할 수 있다.