
useMemo 훅을 다뤄보기전에 먼저 메모이제이션 개념에 대해서 다뤄보자.
| 한 번 계산한 결과는 저장하여 동일한 입력이 들어왔을 때, 재계산하지않고 저장하고있던 값을 꺼내서 반환하는 것으로 불필요한 반복계산 작업을 예방하여 성능을 향상시킬수 있다
그렇다면 메모이제이션을 사용하기 적절한 상황은 언제일까?
순수함수재귀함수, 데이터 처리, 복잡한 수학 등🧐 재귀함수란?
함수 내부에서 자기 자신을 다시 호출하는 함수로, 무한 루프에 빠지지 않도록 종료조건 명시가 필요하다.
메모이제이션을 사용하면 성능을 향상시킬 수 있다고 했다.
성능향상여부는 어떻게 확인할 수 있을까?
[브라우저 개발자 도구 → Performance 탭 → CPU 설정] 에서 Throttle 값을 조절하여 속도를 인위적으로 느리게 만들어 저사양 환경에서의 성능을 시뮬레이션하여 성능향상여부를 확인할 수 있다.
더불어 실제 사용자가 겪을 수 있는 성능 문제를 개발 단계에서 미리 확인하고 문제점을 발견할 수 있기때문에 개발시 개발자 도구를 잘 활용해야겠다.

| 리렌더링 사이에 계산 결과(값)를 캐싱(저장)할 수 있게 해주는 리액트 훅이다.
메모이제이션 기술을 활용하여 이전 값과 동일하다면 재계산없이 캐시된 값을 반환하고, 특정 값(의존성)이 변경되었을때만 재계산을 수행하는 최적화 Hook이다.
그 외의 경우에는 렌더링시 이전 결과를 재사용하기때문에 성능향상에 도움이 된다.
⇒"useMemo는 종속성이 변경되기 전까지는 재렌더링 사이의 계산 결과를 캐싱(임시저장)한다"
const cachedValue = useMemo(calculateValue, dependencies)
매개변수
calculateValue dependencies 가 변경되지 않았을 때의 값과 동일한 값을 반환한다.dependencies가 변경되었을 경우에는 calculateValue를 호출하여 결과를 반환하고 반환된 값을 재사용할 수 있게 저장한다.dependenciescalculateValue내에서 참조되는 모든 반응형 값(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가 변경되지않으면 리렌더링하지 않는다.
| 리렌더링간에 함수 참조값을 캐싱(저장)할 수 있게 해주는 리액트 훅이다.
// 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 가 이전과 동일할 경우 같은 함수를 반환하고, 변경된 경우 새로운 함수를 반환하고 나중에 재사용할 수 있도록 반환하는 새로운 함수를 저장한다.dependenciesfn내에서 참조되는 모든 반응형 값(props, state, 컴포넌트 바디에 선언된 함수 및 변수)들의 목록이다.[dep1, dep2, dep3])Object.is를 통해 의존성들의 이전 값과 현재 값을 비교한다.반환값
fn 함수fn 함수fn 함수특징
위 세가지는 메모이제이션을 기반으로 한다는 것이 공통점인데, 메모이제이션의 대상이 다르다.
| 구분 | memo | useMemo | useCallback |
|---|---|---|---|
| 대상 | 컴포넌트 | 계산된 값 | 함수 (참조값) |
| 감싸는 대상 | 함수형 컴포넌트 | 함수형 컴포넌트 내에 있는 일반 함수들 | 함수형 컴포넌트 내에 있는 일반 함수들 |
| 사용효과 | 부모 컴포넌트 리렌더링 상황에서도 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