[멋쟁이사자처럼 부트캠프 프론트엔드 14기/REACT] useMemo & useCallback

minji·2025년 9월 2일
post-thumbnail

"한 번이라도 계산한건 내가 다 기억하고 있어!"


useMemo 훅을 다뤄보기전에 먼저 메모이제이션 개념에 대해서 다뤄보자.

메모이제이션(memoization)이란?

| 한 번 계산한 결과는 저장하여 동일한 입력이 들어왔을 때, 재계산하지않고 저장하고있던 값을 꺼내서 반환하는 것으로 불필요한 반복계산 작업을 예방하여 성능을 향상시킬수 있다

그렇다면 메모이제이션을 사용하기 적절한 상황은 언제일까?

  • 입력값이 같으면 결과도 항상 동일한 함수 ⇒ 순수함수
  • 계산비용이 큰 함수 ⇒ 재귀함수, 데이터 처리, 복잡한 수학
  • 동일한 연산을 여러 번 반복해서 호출하는 경우
  • 상호작용 큰 경우(페이지 or 전체섹션 교체)보단 세분화된 경우(그림편집기, 도형이동)

🧐 재귀함수란?
함수 내부에서 자기 자신을 다시 호출하는 함수로, 무한 루프에 빠지지 않도록 종료조건 명시가 필요하다.

메모이제이션을 사용하면 성능을 향상시킬 수 있다고 했다.
성능향상여부는 어떻게 확인할 수 있을까?
[브라우저 개발자 도구Performance 탭CPU 설정] 에서 Throttle 값을 조절하여 속도를 인위적으로 느리게 만들어 저사양 환경에서의 성능을 시뮬레이션하여 성능향상여부를 확인할 수 있다.
더불어 실제 사용자가 겪을 수 있는 성능 문제를 개발 단계에서 미리 확인하고 문제점을 발견할 수 있기때문에 개발시 개발자 도구를 잘 활용해야겠다.


useMemo란?

| 리렌더링 사이에 계산 결과()를 캐싱(저장)할 수 있게 해주는 리액트 훅이다.

메모이제이션 기술을 활용하여 이전 값과 동일하다면 재계산없이 캐시된 값을 반환하고, 특정 값(의존성)이 변경되었을때만 재계산을 수행하는 최적화 Hook이다.
그 외의 경우에는 렌더링시 이전 결과를 재사용하기때문에 성능향상에 도움이 된다.

⇒"useMemo종속성이 변경되기 전까지는 재렌더링 사이의 계산 결과를 캐싱(임시저장)한다"

const cachedValue = useMemo(calculateValue, dependencies)

매개변수

  • calculateValue
    • 캐싱하려는 값을 계산하는 함수이다.
    • 순수해야한다.
    • 인자를 받지않고, 모든 타입의 값을 반환할 수 있어야한다.
    • 초기 렌더링 이후 리액트는 마지막 렌더링 이후 dependencies 가 변경되지 않았을 때의 값과 동일한 값을 반환한다.
    • dependencies가 변경되었을 경우에는 calculateValue를 호출하여 결과를 반환하고 반환된 값을 재사용할 수 있게 저장한다.
  • dependencies
    • calculateValue내에서 참조되는 모든 반응형 값(props, state, 컴포넌트 바디에 선언된 함수 및 변수)들의 목록이다.
    • 인라인 형태로 작성되어야한다. ([dep1, dep2, dep3])
    • Object.is를 통해 의존성들의 이전 값과 현재 값을 비교한다.

반환값

  • (초기 렌더링) 인자없이 calculateValue를 호출한 결과값
  • (종속성 미변경시) 마지막 렌더링에서 저장된 값
  • (종속성 변경시) calculateValue 재호출 후 반환된 값

반환값을 캐싱하는 것이 메모이제이션(memoization)이며, 관련된 훅이 useMemo 이다!

useMemo 는 성능향상 즉, 성능최적화가 목적인 리액트 훅이므로 목적을 위해서만 사용해야한다는 점을 잊지말자!

그럼 여기서 성능최적화는 좋은 것이니까 모든 경우에 useMemo 를 적용시키면 되지않을까라는 궁금증이 생긴다.
답은 "그렇지 않다" 이다.

useMemo 로 최적화하기 유용한 경우는 다음과 같으며, 다음 경우들을 제외하고는 useMemo 를 사용하는 것에 대한 이점이 없다.

  • 입력하는 계산이 눈에 띄게 느리고 종속성 변경이 거의 없는 경우
  • memo 로 감싸진 컴포넌트에 prop로 전달할 경우
  • 전달한 값을 나중에 일부 훅의 종속성으로 이용할 경우

더불어 메모이제이션을 불필요하게 만들지않게 하기위한 지켜야하는 몇 가지 규칙이 있는데 다음과 같다.
1. 다른 컴포넌트를 감쌀 때 JSX를 자식처럼 받아들이도록 하자
2. 지역 상태를 선호하며 필요 이상의 상태끌어올리기는 하자
3. 순수한 렌더링 조직을 유지하자
4. 상태를 업데이트하는 Effect 의 불필요한 사용을 자제하자
5. Effect 에서 불필요한 종속성을 제거하자


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

💡참고내용
memo 를 사용하면 컴포넌트의 props가 변경되지 않은 경우 리렌더링을 건너뛸 수 있다.
형태는 다음과 같다.

const MemoizedComponent = memo(SomeComponent, arePropsEqual?)

컴포넌트를 memo 로 감싸면 해당 컴포넌트는 일반적으로 부모 컴포넌트가 리렌더링 되어도 props가 변경되지 않으면 리렌더링 되지 않는다.
이 메모이제이션 기술은 성능최적화를 하지만, 무조건적으로 보장하는 것은 아니기때문에 리렌더링이 될 수도 있다는 점은 참고해야한다.

매개변수

  • SomeComponent: 메모하려는 대상 컴포넌트
  • ardPropsEqual: (optional) 컴포넌트의 이전Props 와 새로운 Props의 두 가지 인수를 받는 함수지만, 리액트는 일반적으로 해당 옵션을 지정하지 않고 Object.is 로 Props를 비교한다.

    반환값
  • 새로운 컴포넌트: SomeComponent 컴포넌트와 동일하게 동작하나 부모가 리렌더링되더라도 Props가 변경되지않으면 리렌더링하지 않는다.

useCallback이란?

| 리렌더링간에 함수 참조값을 캐싱(저장)할 수 있게 해주는 리액트 훅이다.

// 1. 기본형태
const cachedFn = useCallback(fn, dependencies)

// 2. 레퍼런스
import { useCallback } from 'react';

export default function ProductPage({ productId, referrer, theme }) {
  const handleSubmit = useCallback((orderDetails) => {
    post('/product/' + productId + '/buy', {
      referrer,
      orderDetails,
    });
  }, [productId, referrer]);

매개변수

  • fn
    • 캐싱할 함수값으로 어떤 인자반환값도 가질 수 있다.
    • 첫 렌더링시 이 함수를 반환한다. (호출 ❌)
    • 다음 렌더링에서 dependencies 가 이전과 동일할 경우 같은 함수를 반환하고, 변경된 경우 새로운 함수를 반환하고 나중에 재사용할 수 있도록 반환하는 새로운 함수를 저장한다.
  • dependencies
    • fn내에서 참조되는 모든 반응형 값(props, state, 컴포넌트 바디에 선언된 함수 및 변수)들의 목록이다.
    • 의존성 목록은 항목 수가 일정해야하며, 인라인으로 작성해야한다. ([dep1, dep2, dep3])
    • Object.is를 통해 의존성들의 이전 값과 현재 값을 비교한다.

반환값

  • (최초 렌더링) 매개변수로 전달된 fn 함수
  • (후속 렌더링 - 의존성 변경 ❌) 이미 저장해두었던 fn 함수
  • (후속 렌더링 - 의존성 변경 ✅) 현재 렌더링 중에 전달된 fn 함수

특징

  • 내부에 중첩함수를 추가작성하지 않아도 된다.
  • 자식 컴포넌트의 props로 함수를 넘길 때 주로 사용된다. (불필요한 렌더링 방지목적)

🧩 memo vs. useMemo vs. useCallback

위 세가지는 메모이제이션을 기반으로 한다는 것이 공통점인데, 메모이제이션의 대상이 다르다.

구분memouseMemouseCallback
대상컴포넌트계산된 값함수 (참조값)
감싸는 대상함수형 컴포넌트함수형 컴포넌트 내에 있는 일반 함수들함수형 컴포넌트 내에 있는 일반 함수들
사용효과부모 컴포넌트 리렌더링 상황에서도 props 미변경시 리렌더링 ❌의존성 미변경시 이전 값 재사용의존성 미변경시 이전과 동일함수참조 재사용

요약해보자면,

  • 컴포넌트 자체를 기억해야해! ⇒ memo
  • 을 기억해야해! ⇒ useMemo
  • 함수 타입을 기억해야해! ⇒ useCallback
  • 성능저하가 뚜렷하게 발견되는 경우에 한해서만 사용하기를 권장한다. 남용할 경우 오히려 코드 복잡도 증가와 기본 리렌더링 최적화와 충돌이 발생할 수 있다.

🔗 참고자료
https://ko.react.dev/reference/react/useMemo
https://ko.react.dev/reference/react/memo
https://ko.react.dev/reference/react/useCallback
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/is

profile
FE Developer .mjs

0개의 댓글