코딩/기초

[React] useMemo와 useCallback

아하형 2025. 5. 20. 20:21

1. 메모이제이션과 React 렌더링 이해하기

메모이제이션은 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다. React에서 이 개념은 리렌더링 과정에서 특히 중요하다.

React에서 리렌더링이 발생하는 주요 조건은 다음과 같다:

  • 컴포넌트의 state가 변경될 때
  • 부모 컴포넌트로부터 새로운 props가 전달될 때
  • 부모 컴포넌트가 리렌더링될 때

이러한 상황에서 React는 Virtual DOM을 사용하여 변경된 부분만 실제 DOM에 반영하지만, 불필요한 계산이나 함수 재생성이 계속 발생하면 성능 저하로 이어질 수 있다.

2. useMemo: 계산 결과의 메모이제이션

2-1. 개념과 구조

useMemo는 계산 결과를 캐싱하여 불필요한 재계산을 방지하는 훅이다.

const value = useMemo(() => {
  return calculate();
}, [dependencies]);

의존성 배열 내의 값이 변경될 경우에만 함수를 재실행하고, 그렇지 않으면 이전 결과를 재사용한다.

단, 의존성 배열이 []일 경우에는 컴포넌트가 처음 마운트될 때 반드시 한 번은 함수가 실행된다는 점에 유의해야 한다.

2-2. 활용 사례

  • 복잡한 계산: 정렬, 필터링, 평균 계산 등 비용이 큰 연산
  • 객체/배열 참조 동일성 유지: props 최적화 및 React.memo 활용 시
const getAverage = numbers => {
  console.log('평균값 계산 중..');
  if (numbers.length === 0) return 0;
  const sum = numbers.reduce((a, b) => a + b);
  return sum / numbers.length;
};

const Average = () => {
  const [list, setList] = useState([]);
  const [number, setNumber] = useState('');

  const avg = useMemo(() => getAverage(list), [list]);

  // ...
};

3. useCallback: 함수의 메모이제이션

3-1. 개념과 구조

useCallback은 함수 자체를 메모이제이션하여, 의존성 배열의 값이 변경될 때만 함수를 새로 생성한다.

const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

이 훅은 React.memo와 함께 사용할 때 가장 큰 효과를 발휘한다. 전달되는 함수의 참조값이 변하지 않아 불필요한 자식 컴포넌트 리렌더링을 방지할 수 있다.

3-2. 활용 사례

  • 자식 컴포넌트에 콜백 함수 전달: React.memo 사용 시 유용
  • useEffect 의존성 배열 안정화
const ParentComponent = () => {
  const [count, setCount] = useState(0);

  const handleClick = useCallback(() => {
    setCount(count + 1);
  }, [count]);

  return <ChildComponent onClick={handleClick} />;
};

4. 최적화 훅 사용 시 주의사항

4-1. 남용 시 발생하는 문제

  • 코드 가독성 저하
  • 메모리 사용량 증가
  • 초기 렌더링 비용 증가
  • 의존성 배열 관리의 복잡성

실제로는 useMemo, useCallback을 잘못 사용했을 때 최적화 효과는커녕 오히려 성능이 나빠지는 경우도 존재한다. 따라서 사용 시점과 대상을 신중하게 판단해야 한다.

4-2. 효율적인 사용을 위한 권장사항

  • 복잡한 계산에만 useMemo 사용
  • 참조 동일성이 필요한 경우에만 사용
  • 의존성 배열이 자주 변경되는 경우 피하기
  • 성능 문제가 발생한 이후 점진적으로 적용
  • useCallback 대신 함수형 업데이트 사용 권장
// 이렇게 하지 말고
const handleAddTodo = useCallback((text) => {
  setTodos([...todos, newTodo]);
}, [todos]);

// 이렇게 하세요
const handleAddTodo = useCallback((text) => {
  setTodos(prevTodos => [...prevTodos, newTodo]);
}, []);

또한 필요 시 React 개발자 도구의 Profiler 탭을 활용하여 실제로 렌더링 병목이 발생하는 부분을 식별한 후 메모이제이션 훅을 적용하는 것도 좋은 전략이다.

5. 결론

useMemouseCallback은 React 애플리케이션의 성능을 최적화하는 유용한 도구이다. 이 훅들은 리렌더링 시 발생할 수 있는 불필요한 계산과 함수 재생성을 줄여 성능을 개선한다.

그러나 성능 최적화는 필요할 때 신중하게 접근해야 하며, 단순한 연산이나 자주 바뀌는 상태에 무작정 적용하는 것은 오히려 역효과를 불러올 수 있다.

효율적인 React 애플리케이션을 위해서는 메모이제이션의 이점을 정확히 이해하고, 실제 성능 병목이 발생하는 영역에 선택적으로 적용하는 것이 중요하다.