모든 글

[배우는 중] useMemo를 일단 다 붙였다가 혼났다

파생 상태를 배우고 나니 "매 렌더 계산, 느리지 않나?" 걱정에 useMemo·useCallback을 반사적으로 다 붙였다. 그게 왜 틀린 접근이었는지, 메모이제이션 3종의 역할과 "언제 쓰나"를 공부하며 이해한 과정을 남긴다.

배우는 중(Learning log) — 완성된 정리가 아니라, 혼자 공부하다 막힌 지점과 그걸 넘은 과정을 그대로 남기는 시리즈입니다. 지금 제 이해라서 틀릴 수 있고, 나중에 알게 되면 정정 노트를 답니다.

지난 글에서 “화면에 보이는 값은 state로 두지 말고 계산하라”를 배웠어요.

검색+필터 목록에서 filtered를 state에서 빼고 매 렌더마다 다시 계산하도록 바꾼 거죠.

그랬더니 바로 다음 걱정이 생겼어요.

그럼 렌더할 때마다 filter+sort를 다시 도는 거잖아.

느리지 않나?

그래서 이번엔 겁먹고 다 붙였어요.

계산이 있으면 useMemo, 함수를 자식에 넘기면 useCallback. 기계적으로요.

const filtered = useMemo(
  () => items.filter((it) => it.name.includes(query)).sort(sortBy(sort)),
  [items, query, sort]
);
const count = useMemo(() => filtered.length, [filtered]);   // ← 이건 좀…
const handleClick = useCallback((id) => setSelected(id), []);
const headerStyle = useMemo(() => ({ padding: 8 }), []);     // ← 이것도…

“성능 최적화”라는 말에 주눅이 들어서, 안 붙이면 큰일 나는 줄 알았거든요.

그런데 코드가 더 복잡해지기만 했어요.

한 줄이면 됐던 게 useMemo(() => …, [의존성])로 길어지고, 의존성 배열에 뭘 넣을지 매번 고민해야 했고요.

count처럼 filtered.length면 끝나는 걸 감싸니 좀 우스웠어요.

이거 정말 빨라진 거 맞나?

체감상 달라진 건 없는데 코드만 장황해졌거든요.

공부해보니 메모이제이션 도구가 셋인데, 역할이 서로 달랐어요.

React.memo는 props가 같으면 그 컴포넌트의 리렌더를 생략하고, useMemo는 무거운 계산 결과를 캐시하고, useCallback은 함수의 참조를 유지해요 — 자식 memo와 짝으로 쓸 때.

그러다 이 한 문장에 걸렸어요.

메모이제이션도 비교 비용이 듭니다. 측정 없이 모든 곳에 붙이면 오히려 느리고 코드만 복잡해집니다. “느린 게 확인된 곳”에만 쓰세요.

그러니까 useMemo는 공짜가 아니었어요.

캐시를 쓰려면 매 렌더마다 “의존성이 바뀌었나”를 비교해야 하고, 이전 값을 메모리에 들고 있어야 해요.

계산이 가벼우면 이 비교·보관 비용이 계산을 아끼는 이득보다 커요.

count = filtered.length를 감싼 건, 덧셈 한 번 아끼자고 장부를 따로 쓴 셈이었죠.

useCallback을 여기저기 붙이면 안 되는 이유도 그제야 보였어요.

이건 자식이 React.memo로 감싸져 있을 때 그 자식의 리렌더를 막아주는 게 목적인데, 제 handleClick을 받는 자식은 memo도 아니었거든요.

짝 없는 useCallback은 그냥 장식이었어요.

그래서 순서를 뒤집었어요.

기본은 안 감싼다.

먼저 평범하게 계산으로 짜고, 느린 게 실제로 보일 때 — 목록이 수천 개라거나, Profiler로 특정 컴포넌트가 느리다고 찍힐 때 — 그때 그 지점만 감싼다.

제 오해는 “최적화 = 미리 다 감싸두는 것”이었어요.

아니었어요.

최적화는 측정해서 느린 곳을 찾아 거기만 손대는 거고, 그 전의 useMemo는 최적화가 아니라 추측이었어요.

느리지도 않은 이미지를 전부 lazy-load 걸어놓고 “최적화했다”던 거랑 비슷했달까요.

덤으로, React 19 컴파일러는 이 메모이제이션을 상당 부분 자동으로 해주는 방향이더라고요.

그 컴파일러 글을 쓰면서도 정작 “내가 손으로 언제 memo를 해야 하나”는 몰랐던 거예요.

손으로 useMemo를 덕지덕지 붙이는 시대는 오히려 저물고 있었습니다.

아직 못 잡은 건 그 “확인”이에요.

Profiler의 빨간 막대가 몇 ms부터 손대야 할 신호인지 감이 없어요.

컴파일러를 켜면 제가 이미 쓴 useMemo는 지워야 하는지 둬도 되는지도 아직이고요.

혹시 “최적화해야 할 것 같아서” useMemo부터 붙이는 중이면 — 먼저 안 붙이고 짜보세요.

느린 게 눈에 보일 때 Profiler로 범인을 찾아 거기만.

N
NACRE
Writing on less · Seoul
다른 글 보기

댓글

  1. 불러오는 중…