--- title: "useMemo, 컴파일러에 맡겨도 될까" description: "React Compiler 1.0이 실제로 만드는 코드를 열어 보고, 조용히 건너뛰는 컴포넌트와 잡지 못하는 버그를 확인한 기록" date: 2025-10-13 updated: 2026-09-28T04:12:00.000Z tags: [React, Next.js] source: https://crowrish.com/blog/usememo-react-compiler site: "CrowRish Blog" --- > 2025년 10월에 쓴 글을 2026년 9월에 다시 썼습니다. 2025년 10월, Next.js 16 베타가 나온 직후에 이 주제로 글을 썼습니다. 다시 읽어 보니 발표 자료를 옮겨 적은 수준이었습니다. 컴파일러가 "알아서 최적화해 준다"고 써 놓고, 정작 컴파일러가 뭘 만드는지는 한 번도 열어 보지 않았습니다. 그래서 이번엔 직접 돌려 봤습니다. `babel-plugin-react-compiler` 1.0으로 예제 컴포넌트와 이 블로그 코드를 컴파일하고, 결과를 보면서 원래 질문에 다시 답해 봤습니다. ## 처음 질문 ```typescript function ProductList({ products, onSelect }) { const filtered = products.filter((p) => p.inStock); return ( ); } ``` 예전 같으면 여기서 고민이 시작됩니다. `filtered`는 `useMemo`로 감쌀지, `onSelect`에 넘기는 화살표 함수는 `useCallback`으로 뺄지. 팀마다 답이 다르고, 결국 "일단 다 감싸자"가 이기는 경우가 많았습니다. React 팀도 이걸 문제로 봤습니다. Meta 안에서 수동 메모이제이션이 들어간 PR은 전체의 8% 정도였는데, 그런 PR은 작성 시간이 31~46% 더 걸렸다고 합니다. 감싸는 것보다 의존성 배열을 맞추는 데 품이 들기 때문입니다. ## 컴파일러가 만든 코드 위 컴포넌트를 컴파일하면 이렇게 나옵니다. ```javascript import { c as _c } from "react/compiler-runtime"; function ProductList(t0) { const $ = _c(7); const { products, onSelect } = t0; let t1; if ($[0] !== onSelect || $[1] !== products) { const filtered = products.filter(_temp); let t2; if ($[3] !== onSelect) { t2 = (p_0) => onSelect(p_0.id)} />; $[3] = onSelect; $[4] = t2; } else { t2 = $[4]; } t1 = filtered.map(t2); $[0] = onSelect; $[1] = products; $[2] = t1; } else { t1 = $[2]; } let t2; if ($[5] !== t1) { t2 = ; $[5] = t1; $[6] = t2; } else { t2 = $[6]; } return t2; } function _temp(p) { return p.inStock; } ``` 처음 보면 낯설지만 규칙은 단순합니다. `_c(7)`은 이 컴포넌트가 쓸 캐시 칸 7개로, 렌더링이 끝나도 남아 있습니다. 그다음부터는 지난번 값과 지금 값을 `!==`로 비교해서, 바뀌었으면 다시 계산하고 아니면 칸에 넣어 둔 결과를 꺼냅니다. `useMemo`가 하던 일을 코드로 풀어 쓴 셈입니다. 눈에 띈 건 두 가지였습니다. 하나는 `