React 공식문서 스터디

useMemo (1)

려낭 2024. 6. 17. 22:38

useMemo 란?

React에서 성능 최적화를위해 사용하는 훅이다.

컴포넌트가 화면에 그려질 때, 어떤 계산이 매우 오래 걸리거나 많은 자원을 사용할 수 있다.

이런 계산을 자주 하면 앱이 느려질 수 있는데 useMemo는 이런 계산을 효율적으로 처리할 수 있도록 도와준다.

 


 

레퍼런스

 

컴포넌트의 최상위 레벨에 있는 'useMemo'를 호출하여 재렌더링 사이의 계산을 캐싱한다.

 

import { useMemo } from 'react';

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

 

매개변수

 

계산 함수 ('calculateValue') : 실제로 계산을 하는 함수.

의존성 배열 ('dependencies') : 이 배열에 포함된 값이 변경될 때만 계산을 다시 한다.

 

dependencies가 여러분이 좋아하는 재료라고 하고, calculateValue는 그 재료를 사용해 요리하는 과정이라고 하자.

useMemo는 그 요리(결과)를 기억해서 같은 재료가 있을 때는 새로 요리하지 않고, 이전에 만든 요리를 다시 사용하는 것과 같다. 

이렇게 하면 매번 새로 요리하지 않고, 필요할 때만 요리해서 시간을 절약할 수 있다.

 

반환값

 

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

 

다음 렌더링에서, 마지막 렌더링에서 저장된 값을 반환하거나 (종속성이 변경되지 않은 경우), calculateValue를 다시 호출하고 반환된 값을 저장한다.

 

주의사항

 

useMemo는 Hook이므로 컴포넌트의 최상위 레벨 또는 자체 Hook에서만 호출할 수 있다.

반복문이나 조건문 내부에서는 호출할 수 없다. 만일 호출이 필요하다면 새 컴포넌트를 추출하고 상태를 그 안으로 옮겨야 한다.

 

Strict Mode에서 React는 실수로 발생한 오류를 찾기위해 계산 함수를 두 번호출한다. 이는 개발환경에서만 동작하는 방식이며,실제 프로덕션 환경에는 영향을 미치지 않는다. 연산함수가 순수하다면, 로직에는 영향을 미치지 않는다. 

 

React는 개발 중이거나 특정 상황에서 캐시를 버릴 수 있다.

상황에 따라 useMemo 대신 상태 변수나 ref 를 사용하는 것이 더 적합할 수 있다.

 

중요!!

이와 같이 반환값을 캐싱하는 것을 memoization 라고 하며, 이 훅을 useMemo라고 부르는 이유이다.

 

사용법

 

비용이 높은 로직의 재계산 생략하기

 

재렌더링 사이에 계산을 캐싱하려면 컴포넌트의 최상위 레벨에서 useMemo를 호출하여 계산을 감싸면 된다.

import { useMemo } from 'react';

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

 

useMemo에 두 가지를 전달해야 한다.

 

  1. () => 와 같이 인수를 받지 않고 계산하려는 값을 반환하는 계산함수 이다.
  2. 계산 내부에서 사용되는 컴포넌트 내의 모든 값을 포함하는 종속성 목록이다

초기 렌더링에서 useMemo에서 얻을 수 있는 값은 계산함수를 호출한 결과값이다.

 

이후 모든 렌더링에서 React는 종속성 목록을 마지막 렌더링 중에 전달한 종속성 목록과 비교한다.

만일 (Object.js 와 비교했을 때) 종속성이 변경되지 않았다면, useMemo는 이전에 이미 계산해둔 값을 반환한다. 그렇지 않다면 React는 계산을 다시 실행하고 새로운 값을 반환한다.

 

즉, useMemo는 종속성이 변경되기 전까지 재렌더링 사이의 계산 결과를 캐싱한다.

 

이 기능이 언제 유용할까? 예시를 통해 살펴보자

 

기본적으로 React는 컴포넌트를 다시 렌더링 할 때마다 컴포넌트의 전체 본문을 다시 실행한다. 예를 들어, TodoList가 상태를 업데이트 하거나 부모로부터 새로운 props를 받으면 filterTodos 함수가 다시 실행된다.

 

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

 

일반적으로 대부분의 계산이 매우 빠르기 때문에 문제가 되지 않는다. 그러나 큰 배열을 필터링 혹은 변환하거나 비용이 많이 드는 계산을 수행하는 경우, 데이터가 변경되지 않았다면 계산을 생략하는 것이 좋다. 만약 todos과 tab이 마지막 렌더링 때와 동일한 경우, 앞서 언급한 것처럼 useMemo로 계산을 감싸면 이전에 계산된 visivleTodos를 재사용할 수 있다.

 

이러한 유형의 캐싱을 메모이제이션 이라고 한다.

 

중요!

성능 최적화를 위해서만 useMemo를 사용해야 한다. 이 기능이 없어서 코드가 작동하지 않는다면 근본적인 문제를 먼저 찾아서 수정해라. 그 후 useMemo를 사용하여 성능을 개선해야 한다.

 

 

컴포넌트 재렌더링 건너뛰기

 

useMemo는 하위 컴포넌트 재렌더링 성능을 최적화하는데 도움이 될 수 있다.

TodoList 컴포넌트가 자식 컴포넌트인 Listd에 visibleTodos를 prop으로 전달한다고 가정해보자.

 

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

 

theme prop 을 토글하면 앱이 잠시 멈추는 것을 확인할 수 있다. 그러나 JSX에서 <List/>를 제거하면 빨라진다. 

이는 List 컴포넌트를 최적화할 가치가 있다는 것을 알려준다.

 

기본적으로 React는 컴포넌트가 다시 렌더링 될 떄, 모든 자식 컴포넌트를 재귀적으로 다시 렌더링한다. 

그러므로 TodoList가 다른 theme로 다시 렌더링 되면 List 컴포넌트 또한 다시 렌더링된다. 다시 렌더링하는 데 많은 계산이 필요하지 않는 컴포넌트는 괜찮지만, 다시 렌더링하는 것이 느려진다면 List를 memo를 통해 감싸서 props가 마지막 렌더링 시점과 동일할 때 다시 렌더링 하는 것을 생략할 수 있다.

 

import { memo } from 'react';

const List = memo(function List({ items }) {
  // ...
});

 

이 변경으로 List는 모든 props가 마지막 렌더링 때와 동일한 경우 다시 렌더링하지 않는다. 여기서 계산을 캐싱하는 것이 중요하다.

useMemo 없이 visibleTodos를 계산한다고 가정해보자.

 

export default function TodoList({ todos, tab, theme }) {
  // 테마가 변경될 때 마다 다른 배열이 표시됩니다.
  const visibleTodos = filterTodos(todos, tab);
  return (
    <div className={theme}>
      {/* ... List의 props는 동일하지 않으며 매번 다시 렌더링 됩니다. */}
      <List items={visibleTodos} />
    </div>
  );
}

 

위의 예시에서 filterTodos 함수는 항상 다른 배열을 생성한다. 이는 {} 객체 리터럴이 항상 새 객체를 생성하는 것과 유사하다. 일반적으로 이는 문제가 되지 않지만 List의 props는 동일하지 않으며 memo를 사용한 최적화가 작동하지 않는다는 것을 의미한다. 이러한 경우 useMemo가 유용하다.

 

export default function TodoList({ todos, tab, theme }) {
  // 재렌더링 사이에 계산을 캐싱하도록 React에 지시합니다...
  const visibleTodos = useMemo(
    () => filterTodos(todos, tab),
    [todos, tab] // ...따라서 해당 종속성이 변경되지 않는 한...
  );
  return (
    <div className={theme}>
      {/* ...List에 동일한 props가 전달되어 재렌더링을 생략할 수 있습니다. */}
      <List items={visibleTodos} />
    </div>
  );
}

 

visibleTodos 연산을 useMemo로 감싸면 다시 렌더링 될 때마가 같은 값을 갖게 할 수 있다. 

특별한 이유가 없는 한 연산을 useMemo로 감싸지 않아도 된다. 이 예제에서는 memo로 감싸진 컴포넌트에 전달하면 재렌더링을 건너뛸 수 있기 때문이다.

'React 공식문서 스터디' 카테고리의 다른 글

useCallback (1)  (0) 2024.06.18
useMemo (2)  (0) 2024.06.18
useEffect, useLayoutEffect 발표자료  (0) 2024.06.14
useLayoutEffect  (2) 2024.06.11
useEffect (3)  (0) 2024.06.10