본문 바로가기

FRONTEND

useMemo

https://unsplash.com/ko/%EC%82%AC%EC%A7%84/%EC%97%AC%EB%9F%AC-%EA%B0%80%EC%A7%80-%EB%B9%9B%EA%B9%94%EC%9D%98-%EA%BD%83-%EA%B7%B8%EB%A6%BC-iMdsjoiftZo

 

useMemo의 memo는 memoization을 뜻합니다. memoization은 동일한 값을 반환하는 함수를 반복해서 호출해야 할 경우 반복적으로 호출하는 것이 아니라 맨 처음 값을 계산할 때 해당 값을 메모리에 저장(캐싱)하여 해당 값이 필요할 때마다 함수를 반복적으로 호출하지 않고 메모리에 저장된 값을 재사용하는 방식을 말합니다.

 

useMemo에 대해서 살펴보기 전 다음 내용에 대한 이해가 필요합니다.

 

1) 함수형 컴포넌트는 자바스크립트 함수(객체)입니다.
2) 자바스크립트 함수는 일급객체로 취급되기 때문에 함수를 변수에 할당할 수 있습니다.
3) 컴포넌트를 렌더링 한다는 것은 해당 컴포넌트(함수)를 호출(실행)시킨다는 의미이므로 컴포넌트가 렌더링 되면 컴포넌트 내부의 변수는 전부 초기화 됩니다.

 

먼저 아래의 React 컴포넌트를 살펴 보겠습니다. Component의 내부에 선언된 변수 value에는 함수  calculate에 의해 계산된 숫자 10이 담기게 됩니다. Component는 props나 state의 변경이 있을 때마다 렌더링(호출, 실행)이 되고 렌더링이 될 때마다 calculate 함수가 호출되어 value 값을 10으로 초기화 하게 됩니다.

function Component () {
  const calculate = () => {
    return 10;
  }
  
  const value = calculate();
  
  return(
    <div>{value}</div>
  );
}

 

만약 calculate 함수가 굉장히 무거운 연산을 하는 함수라면 지금과 같이 계속해서 실행키시는 것이 비효율적일 것입니다. 연산 비용이 큰 calculate 함수가 반복해서 실행되어 어차피 동일한 결과 값을 반환할 것이기 때문입니다. 이런 상황에서 useMemo를 사용한다면, 해당 value를 메모리에 저장하여 Component가 렌더링 되어도 calculate 함수를 다시 호출하지 않고 메모리에 저장된 value를 꺼내와서 재사용할 수 있게 해주어 불필요한 작업을 반복적으로 하지 않도록 할 수 있습니다. 

useMemo 소개

형태

const cachedValue = useMemo(calculateValue, dependencies)

calculateValue

useMemo의 첫 번째 인자로 memoization이 필요한 값을 반환하는 callback 함수입니다. 해당 함수는 인자를 받을 수 없습니다. 초기 렌더링시 해당 callback 함수가 실행됩니다. 반환된 값을 저장하여 나중에 재사용합니다.

 

dependencies

useMemo의 두 번째 인자로, 해당 의존성 배열의 값이 변하면 callback 함수를 다시 실행하여 calculateValue에 값을 저장합니다. 빈 배열이 전달될 경우, 컴포넌트가 마운트될 때에만 한 번 callback 함수를 실행하여 값을 저장하고 그 이후에는 동일한 값을 사용하게 됩니다. 다른 훅에서 사용되는 의존성 배열과 같이 Object.is 메서드를 활용하여 비교합니다.

❗️useMemo를 무분별하게 사용하는 것은 성능상 좋지 않습니다. 
useMemo를 사용하여 값을 기억한다는 것은 별도의 메모리 공간을 소비하여 해당 값을 저장하는 것이기 때문에 기억해야할 필요가 없는 값 모두를 저장한다면 메모리 공간의 낭비로 이어질 수 있음을 염두에 두어야합니다.

 

사용

비용이 많이 드는 연산을 해결할 수 있습니다!

예제 코드를 통해 useMemo가 어떤 경우에 사용될 수 있는지 살펴보겠습니다.

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

해당 코드는 할일 목록 리스트를 필터링하는 컴포넌트 입니다. visibleTodos 변수에 filterTodos 함수의 결과 값이 담기게 됩니다. TodoList 컴포넌트가 렌더링 될 때마다 filterTodos 함수는 반복해서 실행될 것입니다. filterTodos 함수가 렌더링 마다 동일한 결과를 반환할 경우, filterTodos 함수의 연산 비용이 높다면 해당 함수가 렌더링 마다 반복적으로 실행되지 않기를 바랄 것입니다. 이런 경우 useMemo를 사용하여 다음과 같이 최적화 작업을 진행할 수 있습니다.

 

import { useMemo } from 'react';

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

 

자식 컴포넌트의 불필요한 리렌더링을 방지할 수 있습니다!

React의 컴포넌트가 렌더링 될 때, 해당 컴포넌트의 자식 컴포넌트 모두 렌더링이 됩니다. 따라서 다음의 예제 코드에서도 TodoList 컴포넌트가 리렌더링이 된다면 자식 컴포넌트인 List 컴포넌트 또한 리렌더링 될것입니다.

 

만약 해당 컴포넌트에서 prop으로 전달되는 theme이 변경될 경우 TodoList 컴포넌트 전체가 리렌더링이 될것입니다. 하지만 생각해보면 theme의 변경으로 인해 List 컴포넌트가 리렌더링 될 필요는 없습니다. List 컴포넌트는 items props에 전달되는 visibleTodos가 변경되기 전까지는 리렌더링이 될 필요가 없습니다.

export default function TodoList({ todos, tab, theme }) {
  // ...
  return (
    <div className={theme}>
      <List items={visibleTodos} />
    </div>
  );
}

 

또한 List 컴포넌트를 렌더링하는데 많은 비용이 요구된다고 가정하면, 다음 예제코드(2번)와 같이 화면이 멈추는 현상이 발생할 것입니다.

따라서 예제코드(1번)과 같이 useMemo를 사용하여 렌더링 최적화 작업을 할 수 있습니다.

 

참고

https://react.dev/reference/react/useMemo

 

'FRONTEND' 카테고리의 다른 글

useCallback  (0) 2024.12.14
React Hooks Introduction & useState  (0) 2024.12.13
useState - setState에 대해서  (0) 2024.12.13
useEffect  (0) 2024.12.11
[NextJS] 공식문서 읽어보기 Learn - SEARCH ENGINE OPTIMIZATION - Crawling and Indexing  (0) 2023.05.26