📙

useMemo 개요

 
 

6.1 useMemo 개요

 
useMemo는 컴포넌트의 성능 최적화를 위하여 사용되는 대표적인 훅입니다. useMemo에서 Memo는 memoization을 뜻하며 이어지는 챕터에서 메모이제이션과 useMemo를 사용하는 이유가 무엇인지 살펴보겠습니다.
 

6.1.2 메모이제이션(Memoization)이란?

 
메모이제이션 기법은 연산의 결과값을 메모리에 저장해 두고 이전 렌더링에서 계산한 값과 현재 렌더링에서의 결과가 동일한 경우, 중복 연산을 할 필요 없이 저장해둔 값을 재사용 할 수 있기 때문에 성능을 최적화할 수 있습니다. 예를 들어 A라는 함수의 전체 실행시간이 10초라고 가정해본다면 A 함수가 실행될 때마다 10초라는 시간이 걸리게 됩니다. 그러나 메모이제이션을 통해 값을 저장하고 함수가 실행될 때 결괏값만 재사용한다면 그만큼 연산을 줄일 수 있으므로 프로그램의 실행 속도를 올릴 수 있게 됩니다.
 
 

6.1.3 함수형 컴포넌트

 
아래의 예제에서 함수형 컴포넌트란 MyComponent를 말하며 말 그대로 함수를 뜻합니다. 또한 함수형 컴포넌트가 렌더링이 된다는 것은 MyComponent를 호출하는 것을 의미하며 함수가 호출될 때마다 함수 내부의 모든 변수를 초기화합니다.
 
function calc(a, b) { return a + b } // 함수형 컴포넌트 const MyComponent() { const result = calc(3,5) return <p>{result}</p> }
 
기본적으로 컴포넌트는 state가 변경 되거나, props가 변경되었을 때마다 리렌더링이 되는데, 그렇다면 리렌더링이 될 때마다 MyComponent를 호출하게 되고 변수 result는 초기화 되므로 매번 calc 함수를 실행할 것입니다. 예제와 같이 간단한 계산만 하고 끝내는 함수라면 상관없겠지만, 만약 calc 함수가 실행될 때마다 약 10초가량의 연산을 한다고 가정해보면 어떨까요?
리렌더링이 세 번만 일어나도 같은 값을 반복 계산하여 result에 할당 해주기 때문에 굉장히 비효율적일 것입니다. 이러한 현상을 해결하기 위해 우리는 useMemo를 사용하여 부하가 걸리는 함수의 결괏값을 메모리에 저장한 뒤, 리렌더링이 될 때 그 결괏값만 가져와서 재사용해 줌으로써 성능을 최적화 할 수 있습니다.
 

6.2 useMemo의 기본 구조

 
// 빈 배열이 들어간 경우 const result = useMemo(() => calc(a, b), []); // 요소가 들어간 경우 const result = useMemo(() => calc(a, b), [item);
 
useMemo의 기본적인 구조는 위와 같습니다. useMemo는 두 개의 인자를 받는데 첫 번째 인자는 콜백함수, 두 번째 인자는 의존성 배열(Array dependencies)이라고 불리는 배열을 받습니다. 첫 번째 인자에 들어가는 콜백함수의 결괏값이 useMemo의 리턴값이 재사용하는 결괏값이 됩니다.
두 번째 인자의 의존성 배열에 요소가 들어가는 경우에는 의존성 배열의 전달 값이 변경될 될 때만 콜백 함수가 실행되는데, 이때 렌더링 과정에서 배열 안의 요소 item의 값이 변경되었는지를 확인하고 값이 변경된 경우에만 콜백 함수를 동작 시켜서 메모이제이션(Memoization)된 값만 다시 계산합니다.
따라서 useMemo는 의존성 배열에서 전달하는 값의 변경 여부에 따라 중복 연산을 최소화 시킬 수 있기 때문에 컴포넌트의 성능을 최적화할 수 있습니다.
 
메모이제이션 기법은 연산의 결과값을 메모리에 저장해 두고 이전 렌더링에서 계산한 값과 현재 렌더링에서의 결과가 동일한 경우, 중복 연산을 할 필요 없이 저장해둔 값을 재사용 할 수 있기 때문에 성능을 최적화할 수 있습니다.
 
 
두 번째 인자에는 위와 같이 빈 배열 또는 요소가 들어갈 수 있는데, 빈 배열이 들어가면 최초로 컴포넌트가 실행되었을 때만 값을 계산하고 이후에는 계속해서 메모이제이션 된 값을 가져와서 재사용 합니다.
두 번째 인자에 요소가 들어가게 된다면 해당 요소가 업데이트될 때만 콜백 함수를 다시 호출하여 메모이제이션 된 값을 업데이트하여 다시 메모이제이션을 해줍니다. 따라서 useMemo를 사용하는 궁극적인 이유는 반복되는 연산을 피하여 컴포넌트의 성능을 최적화하는 데에 있습니다.
 
리액트 공식 문서에서는 useMemo의 기본 구조를 위와 같이 나타내고 있으며, useMemo로 전달된 함수는 렌더링 중에만 실행되며, useMemo는 메모이제이션된 값을 반환한다고 설명하고 있습니다. 이에 대해 useMemo의 기본 구조와 함께 알아보겠습니다.
 
useMemo는 위와 같이 두 개의 인자를 받습니다. 첫 번째 인자는 메모이제이션할 값을 계산해 줄 콜백함수를, 두 번째 인자는 의존성 배열(Array dependencies)이라고 불리는 배열이 전달됩니다. useMemo는 의존성 배열의 전달 값이 변경될 될 때만 콜백 함수가 실행되는데, 이때 렌더링 과정에서 배열 안의 요소 item1과 item2의 값이 변경되었는지를 확인하고 값이 변경된 경우에만 콜백 함수를 동작 시켜서 메모이제이션(Memoization)된 값만 다시 계산합니다. 그러나 만약 의존성 배열의 값이 변경되지 않은 경우에는 렌더링 전에 저장된 마지막 값을 그대로 재사용 합니다. 즉, useMemo는 의존성 배열에서 전달하는 값의 변경 여부에 따라 중복 연산을 최소화 시킬 수 있습니다.
 
정리하면, 메모이제이션 기법은 연산의 결과값을 메모리에 저장해 두고 이전 렌더링에서 계산한 값과 현재 렌더링에서의 결과가 동일한 경우, 중복 연산을 할 필요 없이 저장해둔 값을 재사용 할 수 있기 때문에 성능을 최적화할 수 있습니다.

6.2.1 useEffect 와 useMemo의 차이

 
useMemo는 이전 챕터 3에서 다뤘던 useEffect와 비슷하게 의존성 배열(Array dependencies)에 전달된 값의 변경 여부에 따라 콜백 함수가 실행됩니다. 이 두 가지 Hook의 가장 큰 차이점은 렌더링 과정 중의 동작 여부입니다.
useEffect는 기본적으로 모든 렌더링이 완료된 이후에 실행되며, 렌더링 후 상태가 업데이트 되었을 때를 감지하여 동작하기 때문에 리렌더링을 방지하지 못합니다.
이와 달리 useMemo는 렌더링 과정 중에 실행됩니다. 렌더링 과정 중에 의존성 배열의 값이 변경 되었는지 확인한 후, 값이 변경 되었다면 이전에 저장한 값과 비교하여 값이 다른 경우에만 리렌더링 해줍니다. 그렇기 때문에 useMemo는 메모이제이션 기법을 통해 불필요한 계산을 최소화 시키고, 리렌더링을 막을 수 있으므로 렌더링 성능을 최적화할 때 useMemo Hook을 사용합니다.