useMemo, useCallback, React.memo: 메모이제이션은 공짜가 아니다

크리스가 리액트 성능 최적화 강의를 듣고 감명을 받았다.
"모든 변수와 함수를 메모이제이션(Memoization) 하면 재계산을 안 하니까 무조건 빨라지겠지?"
그날부터 크리스는 모든 코드에 useMemo와 useCallback을 도배하기 시작했다.
// ❌ 최악의 예시: 의미 없는 메모이제이션
const one = useMemo(() => 1, []);
const plus = useCallback(() => console.log('더하기'), []);결과는 어땠을까? 앱은 오히려 더 느려지고 메모리 사용량만 늘어났다.
메모이제이션은 공짜가 아니다. "메모리(RAM)를 써서 연산 시간(CPU)을 사는 것"이다. 비용이 이득보다 크다면 안 하느니만 못하다.
오늘은 리액트의 메모이제이션 3형제가 정확히 언제 필요한지, 그리고 언제 쓰면 안 되는지 명확한 기준을 세워본다.
1. useMemo: 값 기억하기
useMemo는 무거운 계산 결과를 캐싱한다. 의존성 배열(deps)에 있는 값이 바뀌지 않으면, 이전에 계산해둔 값을 그대로 재사용한다.
언제 써야 할까?
"무거운 계산"의 기준이 모호하다면 이렇게 생각하자.
"이 코드가 0.1ms 안에 끝나는가?"
// ✅ 좋은 예시: 배열 필터링 및 정렬 (데이터가 많을 때)
const sortedUsers = useMemo(() => {
return users.filter(u => u.active).sort((a, b) => b.score - a.score);
}, [users]);
// ❌ 나쁜 예시: 단순 변환
// useMemo를 설정하는 비용(객체 생성, 비교)이 더 든다.
const userCount = useMemo(() => users.length, [users]);리액트 공식 문서는 "수천 개의 아이템을 루프 돌리거나 하는 게 아니라면 굳이 쓰지 말라"고 조언한다.
2. React.memo: 컴포넌트 기억하기
React.memo는 고차 컴포넌트(HOC)로, Props가 바뀌지 않으면 리렌더링을 건너뛰게 해준다.
// Child.tsx
const Child = React.memo(({ name, onClick }: { name: string, onClick: () => void }) => {
console.log("자식 렌더링!");
return <div onClick={onClick}>{name}</div>;
});하지만 여기에 함정이 있다. 부모 컴포넌트가 리렌더링 되면, 부모 안에 있는 함수도 새로 만들어진다.
이 문제를 해결하기 위해 등장한 것이 바로 useCallback이다.
3. useCallback: 함수 기억하기
useCallback은 함수를 새로 만들지 않고 기존 함수(참조)를 재사용하게 해준다.
참조 동등성 (Referential Equality)
자바스크립트에서 {} === {}는 false다. 객체(함수 포함)는 내용이 같아도 참조 주소가 다르면 다른 것으로 취급한다.
useCallback은 이 참조 주소를 고정시켜주는 역할을 한다.
// Parent.tsx
function Parent() {
const [count, setCount] = useState(0);
// ✅ useCallback으로 감싸야 Child의 불필요한 렌더링을 막을 수 있다.
const handleClick = useCallback(() => {
console.log("클릭");
}, []); // 의존성이 없으면 영원히 같은 함수 참조 유지
return (
<div>
<button onClick={() => setCount(c => c + 1)}>카운트: {count}</button>
{/* 이제 count가 변해서 부모가 리렌더링 되어도, Child는 조용하다. */}
<Child name="철수" onClick={handleClick} />
</div>
);
}핵심 규칙: useCallback은 단독으로 쓰면 의미가 없다. 자식 컴포넌트가 React.memo로 감싸져 있을 때만 효과가 있다. 그냥 일반 div나 button에 넘기는 함수에는 쓸 필요가 없다.
4. 메모이제이션의 비용
왜 모든 것을 메모이제이션 하면 안 될까?
5. 실전 가이드라인
크리스는 이제 명확한 기준을 세웠다.
⭕ 써야 할 때
❌ 쓰지 말아야 할 때
핵심 정리
성능을 챙겼으니, 이제 앱을 가볍게 다이어트 시킬 차례다. 사용자가 들어오지도 않은 '관리자 페이지' 코드를 미리 다운로드할 필요가 있을까?
코드를 조각내서 필요할 때만 불러오는 기술, Code Splitting을 알아보자.
"번들 사이즈 다이어트: React.lazy와 Suspense를 활용한 Code Splitting 전략" 편에서 계속된다.