React에서 useMemo와 useCallback 이해하기
useMemo와 useCallback의 동작 원리와 사용 시기, 성능 영향 및 흔한 실수 사례를 사례 중심으로 정리한 설명
목차
들어가며
리액트 프로젝트에서 성능 고민은 자주 마주하는 문제다. 특히 불필요한 재렌더링이나 반복 계산이 병목을 만들 때가 있다. 이때 사용하는 대표적인 도구가 useMemo와 useCallback이다. 이 글은 react useMemo 사용법과 useCallback 성능 최적화 관점에서 핵심 개념과 실제 적용 예제를 쉬운 말로 정리한다.
메모이제이션 기본 개념
메모이제이션이란
메모이제이션은 함수의 계산 결과를 저장해 같은 입력이 들어오면 저장된 결과를 재사용하는 기법이다. 리액트에서는 이로써 불필요한 계산을 줄이고 컴포넌트 재렌더링 비용을 낮출 수 있다. 리액트 생태계에서는 useMemo와 useCallback이 주로 사용된다.
언제 메모이제이션을 고려할까
- 계산 비용이 큰 작업이 자주 발생할 때
- 자식 컴포넌트가 props로 전달된 함수나 값 때문에 불필요하게 다시 렌더링될 때
- 리렌더링이 UI 성능에 직접적인 영향을 줄 때
useMemo 사용법과 예제
useMemo는 값의 재계산을 제어한다. 첫 번째 인자에 계산 함수를 넘기고, 두 번째 인자에 의존성 배열을 넣으면 의존성이 바뀔 때만 계산이 다시 된다. react useMemo 사용법은 간단하지만, 남용하면 오히려 비용이 더 늘어난다.
비싼 계산 예제
const heavyCompute = (n) => {
// 예: 큰 수의 소수 판별 루프(단순화된 예)
let count = 0;
for (let i = 2; i <= n; i++) {
let isPrime = true;
for (let j = 2; j < i; j++) {
if (i % j === 0) { isPrime = false; break; }
}
if (isPrime) count++;
}
return count;
};
function Example({ num }) {
const primesCount = React.useMemo(() => heavyCompute(num), [num]);
return <div>소수 개수: {primesCount}</div>;
}
위 예제에서 num이 변하지 않으면 heavyCompute는 다시 실행되지 않는다. 하지만 계산 비용이 작다면 useMemo 오버헤드가 더 클 수 있으니 주의가 필요하다.
주의할 점
- 의존성 배열을 정확히 지정해야 한다. 빠뜨리면 버그를 만든다.
- 매 렌더마다 계산 비용이 낮다면 useMemo가 불필요하다.
- 참조형 값을 반환할 때는 동일성(equality)에 주의한다. 새로운 객체를 반환하면 의미가 없다.
useCallback 사용법과 예제
useCallback은 함수를 메모이제이션한다. 주로 자식 컴포넌트로 함수를 전달할 때 사용한다. 함수의 참조(identity)가 바뀌면 자식 컴포넌트가 재렌더링될 수 있기 때문이다. useCallback은 useMemo와 개념적으로 비슷하지만 반환값이 함수라는 점이 다르다.
자식 재렌더링 방지 예제
const Child = React.memo(function Child({ onClick, label }) {
console.log('Child 렌더링', label);
return <button onClick={onClick}>{label}</button>;
});
function Parent() {
const [count, setCount] = React.useState(0);
const increment = React.useCallback(() => {
setCount(c => c + 1);
}, []);
return (
<div>
<div>카운트: {count}</div>
<Child onClick={increment} label={"증가"} />
</div>
);
}
useCallback 성능 최적화는 자식의 불필요한 재렌더링을 줄여준다. 단, 의존성 배열을 적절히 설정해야 원하는 결과를 얻을 수 있다.
언제 useCallback이 필요하지 않을까
- 자식이 단순하고 렌더 비용이 낮다면 불필요하다.
- 함수를 자주 새로 만들어도 전체 성능에 영향이 미미하면 생략해도 된다.
- 과도한 useCallback 사용은 코드 복잡도만 높인다.
리액트 메모이제이션 예제 정리
다음은 실무에서 자주 쓰이는 패턴 요약이다.
- 비싼 계산: useMemo로 결과 캐시
- 함수 전달: useCallback으로 함수 참조 고정
- 리스트 렌더링: 항목 컴포넌트를 React.memo로 감싸기
- 의존성 배열: 상태와 props를 빠짐없이 포함
체크리스트와 권장 사항
- 프로파일링을 먼저 수행해 병목을 확인한다
- 작은 비용에는 메모이제이션을 적용하지 않는다
- 의존성 관리는 자동화 툴(ESLint hook 규칙)을 활용한다
- 복잡한 로직은 커스텀 훅으로 분리해 테스트한다
마무리
useMemo와 useCallback은 잘 쓰면 강력한 도구다. 하지만 남용은 오히려 성능과 가독성을 해칠 수 있다. 글에서 제시한 예제와 원칙을 바탕으로 react useMemo 사용법과 useCallback 성능 최적화를 균형 있게 적용하면 실무에서 안정적인 성능 개선을 기대할 수 있다. 추가로 리액트 메모이제이션 예제를 직접 적용하면서 경험을 쌓는 것이 가장 좋은 학습 방법이다.