[React] useCallback과 useMemo

Byeonghyeon·2025년 1월 12일

공부

목록 보기
2/21

메모이제이션

useCallback과 useMemo는 둘 다 React에서 성능 최적화를 위해 사용하는 Hook이다. 이 두 Hook은 모두 메모이제이션 기법을 이용한다.

메모이제이션(memoization)이란, 동일한 계산을 반복해야 할 경우 계산한 결과를 메모리에 저장해 두었다가 꺼내 씀으로써 중복 계산을 방지할 수 있게 하는 기법이다.

useCallback

useCallback함수를 메모이제이션한다. 불필요하게 동일한 함수를 다시 생성하지 않도록 기존의 함수를 재활용한다.

매개변수

const cachedFn = useCallback(fn, dependencies)

  • fn : 캐싱할 함숫값이다. 이 함수는 어떤 인자나 반환값도 가질 수 있다.

  • dependencies : fn 내에서 참조되는 모든 반응형 값의 목록. 반응형 값은 props와 state, 그리고 컴포넌트 안에서 직접 선언된 모든 변수와 함수를 포함한다.

반환값

React는 첫 렌더링에서 fn을 반환하고(호출하는 것이 아니다) 다음 렌더링에서 dependencies 값이 이전과 같다면 같은 함수를 다시 반환한다.

반대로 dependencies 값이 변경되었다면 이번 렌더링에서 전달한 함수를 반환하고 나중에 재사용할 수 있도록 이를 저장한다.

사용 예시

const sum = () => x + y;

만약 useCallback을 사용하지 않는다면 위와 같은 함수는 컴포넌트가 렌더링 될 때마다 새롭게 생성된다.

const add = useCallback(() => x + y, [x, y]);

하지만 useCallback을 사용한다면, 컴포넌트가 다시 렌더링 되더라도, 해당 함수가 의존하고 있는 값들(여기서는 x, y)이 바뀌지 않는다면, 함수를 새로 생성하지 않고 기존 함수를 계속 반환한다.

언제 사용해야 할까?

일반적으로 컴포넌트를 렌더링할 때마다 함수를 새로 선언하는 것 자체는 일반적으로 성능에 큰 영향을 끼치지 않는다.

JavaScript에서 함수는 메모리에 저장된 참조를 관리하는 가벼운 객체입니다. 따라서 함수 선언 자체는 비교적 빠른 작업이고, 대부분의 경우 성능 병목의 원인이 되지 않는다고 한다.

성능에 문제가 발생하는 경우는 다음과 같다.

  1. 함수가 자식 컴포넌트로 전달되는 경우

부모 컴포넌트가 리렌더링되면, 새로 생성된 함수를 자식 컴포넌트에 전달하게 된다.
React는 함수 참조가 변경되었는지 확인하고, 참조가 변경되었다고 판단하면 자식 컴포넌트를 리렌더링한다.

  1. 컴포넌트가 복잡하고, 리렌더링이 빈번한 경우

컴포넌트가 매우 자주 리렌더링되거나, 함수 내부에서 추가적으로 무거운 작업(예: API 호출, 데이터 변환 등)을 수행한다면 성능 문제가 발생할 가능성이 커진다.

  1. 함수를 의존성으로 사용하는 경우

useEffect나 다른 훅에서 함수가 의존성으로 사용될 때, 함수가 매번 새로 생성되면 불필요하게 훅이 다시 실행될 수 있다.

즉, (1) 자식 컴포넌트가 없거나, 함수가 자식으로 전달되지 않을 때, (2) 리렌더링 비용이 크지 않을 때는 굳이 useCallback을 사용하지 않아도 된다.

그러므로 useCallback을 사용하기 적절한 경우는,

  • 자식 컴포넌트에 함수를 props로 전달하는 경우
  • 함수가 useEffect의 의존성으로 사용되는 경우
  • 매우 빈번한 리렌더링 상황에서의 성능 최적화

불필요한 useCallback의 사용은 코드의 복잡도만 높일 수 있으므로 주의가 필요하다!

useMemo

useMemo값을 메모이제이션한다. 즉, 연산 비용이 높은 작업의 결과를 메모이제이션하여 불필요하게 다시 계산하지 않도록 한다.

매개변수

useMemo(calculateValue, dependencies)

  • calculateValue : 캐싱하려는 값을 계산하는 함수. 순수해야 하며, 인자를 받지 않고, 모든 타입의 값을 반환할 수 있어야 한다.
  • dependencies : calculateValue 코드 내에서 참조된 모든 반응형 값들의 목록. 반응형 값에는 props, state와 컴포넌트 바디에 직접 선언된 모든 변수와 함수가 포함된다.

반환값

초기 렌더링에서 useMemo는 인자 없이 calculateValue를 호출한 결과를 반환한다.

다음 렌더링에서, 마지막 렌더링에서 저장된 값을 반환하거나, calculateValue를 다시 호출하고 반환된 값을 저장한다.

useMemo 훅이 연산을 수행하면 결과를 메모리에 저장한다. 입력 목록의 값이 하나라도 변경되면 다시 연산을 하게 된다. 이렇게 하면 결과가 항상 최신 상태로 유지되는 동시에, 불필요한 재연산을 피할 수 있게 된다.

사용 예시

function TodoList({ todos, tab, theme }) {
  const visibleTodos = filterTodos(todos, tab);
  // ...
}

이런 코드가 있다고 해보자.

기본적으로 React는 컴포넌트를 다시 렌더링할 때마다 컴포넌트의 전체 본문을 다시 실행한다.

TodoList가 상태를 업데이트하거나 새로운 props를 받으면 filtertodos 함수가 다시 실행된다.

큰 배열을 필터링 혹은 변환하거나 비용이 많이 드는 계산을 수행한다면, 데이터가 변경되지 않았다면 계산을 생략하는게 좋다.

이럴 때 useMemo를 사용하면 된다.

import { useMemo } from 'react';

function TodoList({ todos, tab, theme }) {
  const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
  // ...
}

만약 todos와 tab이 이전 상태와 동일하다면, useMemo를 통해 이전에 계산된 visibleTodos를 다시 사용할 수 있다.

언제 사용해야 할까?

useMemo로 최적화하는 것은 특정 상황에서만 유용하다.

함수나 계산이 매우 가벼운 경우, useMemo를 사용하는 것이 오히려 성능에 부담을 줄 수 있다. useMemo를 사용하면 메모리를 추가로 사용한다. 계산이 가벼운 경우, 굳이 메모리를 사용하면서까지 useMemo를 활용할 필요는 없다.

useMemo를 사용해야 하는 경우는 다음 과 같다.

  • 비싼 계산을 반복하지 않도록 할 때
  • 복잡한 데이터 구조나 객체/배열을 처리할 때
  • 자식 컴포넌트에 복잡한 값을 전달할 때

참고한 곳 : useMemo와 useCallback는 왜, 언제 사용할까?

0개의 댓글