React Memoization
2024-01-16

메모이제이션 (memoization)이란

메모이제이션(memoization)은 컴퓨터 프로그램이 동일한 계산을 반복해야 할 때, 이전에 계산한 값을 메모리에 저장함으로써 동일한 계산의 반복 수행을 제거하여 프로그램 실행 속도를 빠르게 하는 기술이다. 동적 계획법의 핵심이 되는 기술이다. (위키 백과)

쉽게 이야기하면, 컴퓨터의 입장에서 비용이 높은 함수의 호출 결과를 저장해 두었다가, 완전히 동일한 입력으로 함수를 호출하면 새로 함수를 호출하지 않고 이전에 저장해 두었던 결과를 반환해주는 것이다.

리액트의 메모이제이션

리액트에서는 부모 컴포넌트가 변경되면 자식 컴포넌트도 무조건 리렌더링된다. React.memo 를 사용해 컴포넌트를 래핑하면 렌더링이 일어나지 않는다. 렌더 단계에서 컴포넌트를 비교를 거쳤지만 memo로 래핑한 덕분에 props가 변경되지 않으면 렌더링이 생략되므로 커밋 단계도 생략된다.

리액트에서 제공하는 API중 useMemo, useCallback 훅과 고차 컴포넌트인 memo는 리액트에서 발생하는 리렌더링을 최소한으로 줄이기 위해서 제공된다.

메모이제이션도 비용이 든다. 값을 비교하고 렌더링 또는 재계산이 필요한지 확인하는 작업, 이전에 결과물을 저장해 두었다가 다시 꺼내와야 하는 두 가지 비용이 있다.

memo를 썼을 때 지불해야 하는 비용

  • props에 대한 얕은 비교
  • CPU와 메모리를 사용해 이전 렌더링 결과물을 저장해 둬야 하고, 리렌더링이 필요가 없다면 이전 결과물을 사용해야 한다. -> 리액트의 기본적인 재조정 알고리즘 때문에 이전 결과물을 어떻게든 저장해두고 있다.
  • 따라서 memo로 지불해야 하는 비용은 props에 대한 얕은 비교뿐이다. 물론 이 비용 또한 무시할 수는 없다. props가 크고 복잡해진다면 이 비용 또한 커질 수 있다.

memo를 하지 않았을 때 발생할 수 있는 문제

  • 렌더링을 함으로써 발생하는 비용
  • 컴포넌트 내부의 복잡한 로직 재실행
  • 그리고 위 두가지가 모든 자식 컴포넌트에서 반복해서 일어남
  • 리액트가 구 트리와 신규 트리를 비교

메모이제이션은 하지 않는 것보다 메모이제이션 했을 때 이점을 누릴 수 있다. 이것이 비록 섣부른 초기화라 할지라도 했을 때 누릴 수 있는 이점, 그리고 이를 실수로 빠트렸을 때 치러야할 위험 비용이 더 크기 때문에 최적화에 대한 확신이 없다면 가능한 한 모든 곳에서 메모이제이션을 활용한 최적화를 하는 것이 좋다.

useMemo vs useCallback vs React.memo

useMemo

  • 연산의 결과를 기억하고, 의존성 배열이 변경되었을 때만 다시 계산한다.
  • 렌더링 중에 계산이 필요하고 , 해당 계산 결과를 기억하고 싶을 때 사용한다.
const memoizedValue = useMemo((value1, value2) => calcualte(v1,v2) , [v1, v2])

useCallback

  • 함수를 메모이제이션하여 의존성 배열의 값이 변하지 않으면 메모이제이션 한 함수를 반환한다.
  • 자식 컴포넌트에 콜백 함수를 props로 전달하고, 해당 콜백 함수가 불필요하게 다시 생성되지 않도록 할 때 사용한다.
const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

React.memo

  • 함수형 컴포넌트의 렌더링 결과를 메모이제이션하여, props가 변경되지 않으면 이전 렌더링 결과를 재사용한다.
  • 사용 시점: 부모 컴포넌트에서 자식 컴포넌트를 렌더링할 때, 자식 컴포넌트의 불필요한 리렌더링을 방지하고 싶을 때 사용한다.

요약하면, useMemo는 값의 메모이제이션에 사용되고, useCallback은 함수의 메모이제이션에 사용되며, React.memo는 컴포넌트의 렌더링 결과를 메모이제이션한다.

Reference: 리액트 모던 딥다이브