프론트엔드/React

(React) useMemo vs useCallback 차이

그린티_ 2025. 11. 20. 11:33
반응형

서론

React 최적화 관련 글 보다가 useMemo랑 useCallback이라는 Hook을 발견했음
둘 다 성능 최적화를 위한 거라고 하는데, 처음엔 "둘이 뭐가 다른 거야?" 싶었음
코드 보면 사용법도 비슷하고, 둘 다 의존성 배열 쓰는 것도 같아서 헷갈렸음
근데 찾아보니까 하나는 을 메모이제이션하고, 하나는 함수를 메모이제이션하는 거였음
그래서 useMemo와 useCallback의 차이점과 언제 쓰는지 정리해봄


본론

useMemo와 useCallback은 둘 다 불필요한 연산이나 리렌더링을 방지하기 위한 Hook임
하지만 메모이제이션하는 대상이 다름

메모이제이션이 뭐냐면?

메모이제이션(Memoization)은 이전에 계산한 값을 저장해뒀다가 재사용하는 기법

  • 같은 계산을 반복하지 않아도 됨
  • 성능이 향상됨
  • React에서는 불필요한 리렌더링을 줄이는 데 사용함

useMemo: 값을 메모이제이션

useMemo는 계산된 값을 저장해둠
복잡한 연산의 결과를 기억해뒀다가, 의존성 배열의 값이 바뀌지 않으면 이전에 계산한 값을 그대로 사용함

useMemo 사용 예시

import { useState, useMemo } from 'react';

function ExpensiveComponent() {
  const [count, setCount] = useState(0);
  const [input, setInput] = useState('');

  // 복잡한 계산 (시간이 오래 걸림)
  const expensiveValue = useMemo(() => {
    console.log('복잡한 계산 실행됨');
    let result = 0;
    for (let i = 0; i < 1000000000; i++) {
      result += i;
    }
    return result;
  }, [count]); // count가 바뀔 때만 다시 계산

  return (
    <div>
      <p>계산 결과: {expensiveValue}</p>
      <button onClick={() => setCount(count + 1)}>Count 증가</button>
      <input 
        value={input} 
        onChange={(e) => setInput(e.target.value)} 
        placeholder="입력해보세요"
      />
    </div>
  );
}

useMemo 없이 쓰면?

  • input에 글자 하나 입력할 때마다 복잡한 계산이 다시 실행됨
  • 화면이 느려지고 버벅거림

useMemo 쓰면?

  • count가 바뀔 때만 계산이 실행됨
  • input 입력해도 이전에 계산한 값 재사용
  • 성능 상승

useCallback: 함수를 메모이제이션

useCallback은 함수 자체를 저장해둠
의존성 배열의 값이 바뀌지 않으면 이전에 만든 함수를 그대로 재사용함

useCallback 사용 예시

import { useState, useCallback } from 'react';

function ParentComponent() {
  const [count, setCount] = useState(0);
  const [input, setInput] = useState('');

  // 함수를 메모이제이션
  const handleClick = useCallback(() => {
    console.log('버튼 클릭됨');
    console.log('현재 count:', count);
  }, [count]); // count가 바뀔 때만 함수 재생성

  return (
    <div>
      <input 
        value={input} 
        onChange={(e) => setInput(e.target.value)} 
      />
      <ChildComponent onClick={handleClick} />
    </div>
  );
}

function ChildComponent({ onClick }) {
  console.log('ChildComponent 렌더링됨');
  return <button onClick={onClick}>클릭</button>;
}

useCallback 없이 쓰면?

  • 부모 컴포넌트가 리렌더링될 때마다 handleClick 함수가 새로 생성됨
  • 자식 컴포넌트는 props가 바뀐 걸로 인식해서 불필요하게 리렌더링됨

useCallback 쓰면?

  • count가 바뀔 때만 함수가 새로 생성됨
  • input 입력해도 함수는 그대로라서 자식 컴포넌트가 리렌더링 안 됨

useMemo vs useCallback 비교

useMemo

  • 계산된 을 메모이제이션
  • 반환값: 계산 결과 (숫자, 문자열, 객체, 배열 등)
  • 사용 상황: 복잡한 연산 결과를 재사용할 때
const value = useMemo(() => {
  return 복잡한계산();
}, [dependency]);

useCallback

  • 함수 자체를 메모이제이션
  • 반환값: 함수
  • 사용 상황: 자식 컴포넌트에 함수를 props로 넘길 때
const handleClick = useCallback(() => {
  console.log('클릭됨');
}, [dependency]);

핵심 차이

  • useMemo는 () => 값 형태로 값을 반환
  • useCallback은 () => {} 형태로 함수 자체를 반환
  • 사실 useCallback(fn, deps)useMemo(() => fn, deps)와 같음

언제 사용해야 할까?

useMemo 사용 시점

  • 복잡한 계산이 있을 때
  • 배열이나 객체를 생성할 때 (참조값이 바뀌는 걸 방지)
  • 리스트 필터링이나 정렬 같은 연산
const filteredList = useMemo(() => {
  return list.filter(item => item.price > 1000);
}, [list]);

useCallback 사용 시점

  • 자식 컴포넌트에 함수를 props로 넘길 때
  • useEffect의 의존성 배열에 함수를 넣을 때
  • 이벤트 핸들러를 최적화할 때
const handleDelete = useCallback((id) => {
  setItems(items.filter(item => item.id !== id));
}, [items]);

주의할 점

무조건 쓴다고 좋은 건 아님

  • 간단한 연산에는 오히려 성능이 떨어질 수 있음
  • 메모이제이션 자체도 비용이 들기 때문
  • 성능 문제가 실제로 있을 때만 사용하는 게 좋음

React.memo와 함께 써야 효과적임

  • 자식 컴포넌트를 React.memo로 감싸야 useCallback 효과가 제대로 나타남
  • 그냥 쓰면 props는 같아도 자식이 리렌더링될 수 있음
const ChildComponent = React.memo(({ onClick }) => {
  console.log('ChildComponent 렌더링됨');
  return <button onClick={onClick}>클릭</button>;
});

결론

useMemo는 을 메모이제이션하고, useCallback은 함수를 메모이제이션함
둘 다 성능 최적화를 위한 Hook이지만 사용 목적이 다름

  • 복잡한 계산 결과 저장: useMemo
  • 함수를 props로 넘길 때: useCallback

처음엔 "언제 써야 하지?" 싶었는데, 실제로 성능 문제가 생길 때 적용하는 게 답인 것 같음
무분별하게 쓰면 오히려 코드만 복잡해지니까 필요한 곳에만 쓰는 게 중요함
앞으로 최적화 필요한 부분 생기면 useMemo와 useCallback 적절히 활용해봐야겠음!

반응형