메모이제이션 useMemo, useCallback, React.memo

camel·2023년 4월 7일
0
post-thumbnail

저번 프로젝트에서 메모이제이션을 하다가 실패한 경험이 있다.
이유는 컴포넌트화와 메모이제이션을 같이 하다보니 문제가 발생하였고,
결국에는 컴포넌트화를 위해서 메모이제이션을 하지않았다.
그 후에 마무리 단계에서 useMemo, useCallback, React.memo를 하기로 하였지만, 코드스플리팅, 캐싱을 통해 하였고, 이미 시간 단축이 많이 되어진 상태에서 차이가 미미하다고 판단이 되어서 시도하지 않았다.

그래서 이번 프로젝트에 적용하고자 다시 한번 정리하고자 한다.


메모이제이션

메모이제이션(memoization)은 이전에 계산한 결과를 저장하여, 동일한 입력에 대한 함수의 계산을 반복하지 않고 이전에 계산된 결과를 바로 반환하는 기술입니다. 이를 통해 함수의 성능을 향상시킬 수 있습니다.

메모이제이션은 일반적으로 다음과 같은 단계로 구현됩니다.

함수의 인자(argument)를 키(key)로 사용하여 캐시(cache)에서 값을 찾습니다.
캐시에서 값을 찾을 수 없는 경우 함수를 호출하여 값을 계산하고, 계산된 값을 캐시에 저장합니다.
계산된 값을 반환합니다.
캐시는 일반적으로 객체 형태로 구현되며, 키와 값의 쌍으로 저장됩니다.
예를 들어, 다음과 같이 함수를 구현할 수 있습니다.


function fibonacci(n, cache = {}) {
  if (n <= 1) {
    return n;
  }

  if (n in cache) {
    return cache[n];
  }

  const result = fibonacci(n - 1, cache) + fibonacci(n - 2, cache);
  cache[n] = result;

  return result;
}

이 함수는 n번째 피보나치 수를 계산합니다. 먼저, n이 1 이하인 경우에는 n을 반환합니다. 그렇지 않은 경우, 캐시에서 n번째 값을 찾을 수 있는지 확인하고, 캐시에서 값을 찾을 수 없는 경우에는 fibonacci 함수를 재귀적으로 호출하여 n-1번째와 n-2번째 피보나치 수를 계산하고 더하여 결과 값을 구합니다. 계산된 결과 값을 캐시에 저장하고 반환합니다.

이렇게 메모이제이션을 구현하면, 같은 인자를 가지고 여러 번 호출되는 함수에서 성능 향상을 기대할 수 있습니다.






useMemo, useCallback

React의 useMemo와 useCallback 훅은 메모이제이션을 통해 애플리케이션의 성능을 최적화하는 데 사용됩니다. 그러나 이 훅들이 항상 도움이 되는 것은 아닙니다. 특정 상황에서는 오히려 성능을 저하시킬 수도 있습니다.

예를 들어, useMemo를 사용하여 계산이 복잡한 연산을 메모이제이션할 때, 메모리 사용량이 증가할 수 있습니다. 또한, useCallback을 사용하여 콜백 함수를 메모이제이션할 때, 메모이제이션된 콜백 함수를 다시 생성하는 데 드는 오버헤드가 있을 수 있습니다.

따라서, useMemo와 useCallback을 사용하기 전에 다음과 같은 질문에 답할 필요가 있습니다.

판별법

이 코드는 자주 호출되는가?
이 코드가 실행될 때의 결과는 항상 같은가?
이 코드가 계산이 복잡한가?

만약 자주 호출되는 코드이면서 결과가 일정하고, 계산이 복잡한 코드일 경우에는 useMemo나 useCallback을 사용하는 것이 효과적일 수 있습니다. 하지만, 만약 코드가 실행될 때 결과가 항상 같지 않거나, 자주 호출되지 않는 코드라면 useMemo나 useCallback을 사용하는 것은 불필요한 오버헤드를 초래할 수 있습니다.

따라서, useMemo와 useCallback을 사용할 때는 어떤 상황에서 사용하는지에 대해 고려해야 합니다. 필요하지 않은 경우에는 사용하지 않는 것이 좋습니다.

오버헤드

"오버헤드(overhead)"는 어떤 작업을 수행하기 위해 추가로 필요한 작업 또는 비용을 의미합니다.
React 컴포넌트에서는, 컴포넌트 내부에서 상태(state) 또는 속성(props)이 변경될 때마다 컴포넌트가 재렌더링(re-rendering)됩니다. 이 때, 컴포넌트 내부에서 불필요하게 많은 작업이 수행될 경우, 이는 성능 저하로 이어질 수 있습니다. 이러한 불필요한 작업을 "오버헤드"라고 합니다.
따라서, React에서는 성능 최적화를 위해 "오버헤드"를 줄이는 방법들을 제공합니다. 예를 들어, useMemo나 useCallback 훅을 사용하여 컴포넌트 내부에서 불필요하게 많은 작업을 수행하지 않도록 최적화할 수 있습니다. 이렇게 최적화된 컴포넌트는 불필요한 작업을 줄여 성능을 향상시키는 효과를 가집니다.


React.memo

React.memo는 함수형 컴포넌트를 렌더링하는 경우, 이전에 렌더링된 결과를 메모이제이션하여 다음 렌더링이 발생했을 때, 같은 props로 렌더링할 경우 이전에 렌더링된 결과를 재사용할 수 있도록 해주는 React의 훅입니다. 이를 통해 성능 최적화가 가능합니다.

예를 들어, React.memo를 사용하여 컴포넌트를 래핑하면, 해당 컴포넌트가 렌더링될 때마다 이전 결과를 메모이제이션하여 props가 변경되지 않으면 이전에 렌더링된 결과를 사용합니다. 이전 결과를 사용하므로써 불필요한 재렌더링을 줄일 수 있습니다.

React.memo를 사용하는 방법은 간단합니다. 먼저, 컴포넌트를 생성한 후, React.memo 함수로 감싸주면 됩니다. 예를 들어, 다음과 같은 코드는 Header 컴포넌트를 React.memo로 감싸서 props가 변경되지 않는 한 결과를 재사용하게 됩니다

주의할 점

React.memo를 사용할 때는 주의해야 할 점이 있습니다. React.memo는 props가 변경되었을 때만 재렌더링을 수행합니다. 하지만, 컴포넌트 내부의 상태(state)나 컨텍스트(context)가 변경될 경우에는 React.memo가 재렌더링을 수행하지 않습니다. 따라서, 이러한 경우에는 useMemo이나 useCallback과 함께 사용하여 성능 최적화를 수행해야 합니다.

마무리

React.memo는 컴포넌트의 props 변경 여부를 판단하여 렌더링 성능을 최적화해주는 기능을 한다. 따라서 props가 없는 컴포넌트의 경우 React.memo를 사용하는 것은 불필요하다.

useMemo로 최적화하는 경우, 의존성 배열에 값을 넣어주면, 값이 변경될 때마다 새로운 함수를 생성합니다. 그러나 count 값이 변경되지 않으면 이전에 생성된 메모이제이션된 함수를 재사용합니다. 이렇게 하면 성능이 개선됩니다.
useCallback도 마찬가지이다.

그리고 이 세개의 훅은 독립적으로 작용하니 상황에 맞게 잘써주면 효과적으로 메모이제이션을 할 수 있다.

profile
화이팅~ 가보자구

0개의 댓글