(썸네일 이미지는 해당 글을 요약 후 ChatGPT 이미지로 생산한 것 입니다.)
React에서 최적화 얘기하면 항상 따라오는 말이 있다.
useMemo는 값을 메모이제이션 (value)useCallback은 함수를 메모이제이션 (func)
말은 쉬운데, 실제로는 “언제 써야 하는지 / 뭐가 다른지”가 헷갈림...
이번 글은 실무에서 자주 터지는 케이스 중심으로 차이를 정리함.
결론부터
- useMemo:
값(계산 결과)을 캐싱한다. - useCallback:
함수(콜백)의 참조를 캐싱한다. - 그리고 사실상 이렇게 생각해도 된다.
useCallback(fn, deps)는useMemo(() => fn, deps)의 syntax sugar.
즉, 둘 다 “캐싱”이 맞고, 캐싱 대상이 다르다.
왜 캐싱이 필요할까? (React의 리렌더 특성)
컴포넌트는 리렌더될 때마다 함수가 다시 실행된다.
그래서 아래처럼 작성하면:
const obj = { a: 1 }; // 매 렌더마다 새 객체
const arr = [1, 2, 3]; // 매 렌더마다 새 배열
const fn = () => console.log(); // 매 렌더마다 새 함수
obj, arr, fn은 “내용은 같아도” 매번 새 참조(reference)다.
이게 문제되는 대표 상황은:
- React.memo 자식에게 props로 내려줄 때
- useEffect deps에 들어갈 때
- 그리드/테이블/차트처럼 “props 참조 변화”에 민감할 때
useMemo: 값(결과물) 캐싱
기본 형태
const value = useMemo(() => 계산식, deps);
- deps가 바뀌지 않으면
- 이전 렌더에서 계산해둔 값을 그대로 재사용
1) 무거운 계산을 줄이는 용도
const filtered = useMemo(() => {
return list.filter(x => x.active).sort((a, b) => a.name.localeCompare(b.name)); }, [list]);
리렌더가 잦아도 list가 그대로면 계산이 반복되지 않음.
2) “참조 유지”를 위한 용도 (실무에서 더 흔함)
const columns = useMemo(() => [ { key: "name", title: "이름" }, { key: "status", title: "상태" }, ], []);
이렇게 하면 columns 배열 참조가 고정되어
React.memo 자식이나 그리드가 불필요하게 다시 렌더되는 걸 막음.
useCallback: 함수(콜백) 캐싱
기본 형태
const onClick = useCallback(() => { doSomething(id); }, [id]);
- deps가 바뀌지 않으면
- 같은 함수 참조를 재사용
왜 함수 참조가 중요할까?
아래처럼 자식 컴포넌트가 React.memo로 감싸져 있어도:
const Child = React.memo(({ onChange }) => { ... });
부모가 매번 새 함수를 넘기면 자식은 매번 리렌더됨.
<Child onChange={() => setValue(v)} />
// 매 렌더마다 새로운 함수 생성 -> Child도 다시 렌더
그래서:
const onChange = useCallback((v) => setValue(v), []); <Child onChange={onChange} />
이렇게 “함수 참조”를 고정해 리렌더를 줄인다.
한 줄 정리
- useMemo: 값(계산 결과, 객체/배열)을 고정해서 재사용
- useCallback: 함수(콜백) 참조를 고정해서 재사용
- 둘 다 목적은 같다: 불필요한 계산/리렌더를 줄인다