📘 useMemo
"메모이제이션" 기법으로 불필요한 연산을 최적화하는 리액트 훅
메모이제이션이란?
📝 기억해두기, 메모해도기
🧠 메모이제이션 기법이란?

동일한 연산을 반복적으로 수행해야 될 경우 매번 결과 값을 새롭게 만드는 것이 아니라,
최초로 한번 계산할때의 결과 값을 어딘가에 보관해둔 다음 이 연산이 필요해지면 저장된 결과값을 바로 돌려주는 기법
최초로 한번 수행해 연산값을 저장하면 다시 수행할 필요x
→ 성능 안나빠진다.
useMemo를 사용하면 특정 연산값을 기억해 둘 수 있다.
⚠️ Todo 데이터 분석: filter() 메서드의 효율성과 주의점

import React, { useState } from 'react';
const List = ({ todos, onUpdate, onDelete }) => {
...
// 현재의 상태를 분석해 수치로 제공하는 함수
const getAnalyedData = () => {
//legnth라는 값으로 초기화를 한다 -> 왜?
// todos라는 배열의 길이를 구하기 위해서
const totalCount = todos.length;
// 완료된 todo의 개수를 구하기 위해서
// filter메서드를 사용해서 완료된 todo만 필터링한다.
const doneCount = todos.filter((todo) => todo.isDone).length;
const notDoneCount = totalCount - doneCount;
return {
totalCount,
doneCount,
notDoneCount,
};
};
const { totalCount, doneCount, notDoneCount } = getAnalyedData();
return (
...
<div>
<div>total:{totalCount}</div>
<div>done:{doneCount}</div>
<div>not done:{notDoneCount}</div>
</div>
...
);
};
export default List;
⬇️
const getAnalyedData = () => {
//legnth라는 값으로 초기화를 한다 -> 왜?
// todos라는 배열의 길이를 구하기 위해서
const totalCount = todos.length;
// 완료된 todo의 개수를 구하기 위해서
// filter메서드를 사용해서 완료된 todo만 필터링한다.
const doneCount = todos.filter((todo) => todo.isDone).length;
const notDoneCount = totalCount - doneCount;
return {
totalCount,
doneCount,
notDoneCount,
};
};
filter() 메서드를 사용해 isDone이 true인 항목(완료된 todo)을 필터링하고 있다.
배열 전체 길이에서 완료된 개수를 빼서 미완료 개수 계산한다.
하지만 배열의 크기가 커질수록 filter()는 순회 비용이 발생하므로, 성능 측면에서 주의가 필요하다.
이걸 해결하기 위해선?
useMemo를 사용 → 연산 메모이제이션 가능 + 특정 조건 만족시 결과 값 다시 계산 설정 가능
🔍 useMemo 사용 방법
import React, { useState, useMemo } from 'react';
...
const memoizedValue = useMemo(() => {
// 콜백 함수 내용
return 계산된_값;
}, [의존성_배열]);
- () => {} → 콜백 함수 (값을 계산해서 리턴) → 메모이제이션하고 싶은 값을 넣는다.
- [items] → 의존성 배열 (이 값이 바뀌면 다시 계산)
(첫번째 인수: 콜백함수, 두번째 인수: deps)
의존성 배열에 있는 값이 바뀔 때만 콜백 함수가 실행되고,
그 콜백 함수가 리턴한 결과를 useMemo가 기억해서 그대로 다시 돌려준다!
useCallback → "함수 자체를 기억"
useMemo → "값을 기억"
/**
// 현재의 상태를 분석해 수치로 제공하는 함수
const getAnalyedData = () => {
//legnth라는 값으로 초기화를 한다 -> 왜?
// todos라는 배열의 길이를 구하기 위해서
const totalCount = todos.length;
// 완료된 todo의 개수를 구하기 위해서
// filter메서드를 사용해서 완료된 todo만 필터링한다.
const doneCount = todos.filter((todo) => todo.isDone).length;
const notDoneCount = totalCount - doneCount;
return {
totalCount,
doneCount,
notDoneCount,
};
};
*/
const { totalCount, doneCount, notDoneCount } = useMemo(() => {
const totalCount = todos.length;
const doneCount = todos.filter((todo) => todo.isDone).length;
const notDoneCount = totalCount - doneCount;
return {
// 콜백 함수가 반환하는 값은 이 객체들
totalCount,
doneCount,
notDoneCount,
};
}, []);
// const { totalCount, doneCount, notDoneCount } = getAnalyedData();
첫번째 인수로 전달한 콜백함수가 반환하는 값을 그대로 반환한다.
useMemo의 호출의 결과 값을 ⬇️ 구조분해 할당을 통해 받을 수 있다.
const { totalCount, doneCount, notDoneCount }
첫번째 인수로 전달한 콜백함수는 두번째 인수로 전달한 deps를 기준으로 메모이제이션을 한다.
아무것도 deps로 전달안했을 경우
이 콜백 함수는 컴포넌트가 처음 렌더링될 때 한 번만 실행되고, 이후에는 기억해둔 값을 재사용한다.
todos가 외부에서 계속 바뀔 수 있기에 deps에 tods를 넣어준다.
}, [todos]);
deps의 값이 변경되었을 때 callback함수가 다시 실행이 된다.
✨ useMemo Hook을 사용하면, 원하는 연산을 의존성 배열(deps)이 변경될 때만 수행하도록 만들 수 있다.
즉, deps에 있는 값이 바뀌지 않으면, 이전 계산 결과를 그대로 재사용하며, 그 계산된 값을 useMemo가 리턴해준다.
📘 memo - 불필요한 리렌더링 방지하기
React.memo란?
컴포넌트를 인수로 받아, 최적화된 컴포넌트로 만들어 반환
const MemoizedComponent = memo(Component)
------------------ ---------------
ㄴ반환값: ㄴ 인수: 컴포넌트
ㄴ최적화된 컴포넌트
ㄴProps를 기준으로 메모이제이션 됨

MemoizedComponent는 부모 컴포넌트가 리렌더링되더라도, 자신이 받는 props가 변경되지 않으면 리렌더링되지 않는다.
👉🏻 메모이제이션 덕분에 불필요한 리렌더링을 방지할 수 있어, 자동으로 성능 최적화가 이루어진다.
📃 Header.jsx - memoization을 통한 header 최적화
import './Header.css';
import React from 'react';
import { memo } from 'react';
const Header = () => {
return (
<div className="Header">
<h3>오늘은 👇🏻 </h3>
<h1>{new Date().toDateString()}</h1>
</div>
);
};
/** 1. 방법
// 인수로 받은 Header
const memoizedHeader = memo(Header);
// 최적화가 이뤄진 header를 export한다.
export default memoizedHeader;
*/
// 2. 방법
export default memo(Header);
=> 불필요한 리렌더링을 방지 가능
import React from 'react';
import './ToDoItem.css';
import { memo } from 'react';
const ToDoItem = ({ id, isDone, content, date, onUpdate, onDelete }) => {
const onChangeCheckBox = () => {
// 인수로 id전달
onUpdate(id);
};
const onClickDeleteButton = () => {
onDelete(id);
};
return (
<div className="ToDoItem">
<input
// onClick이 아니라 onChange를 사용한 이유는 요소가 인풋요소이기때문
onChange={onChangeCheckBox}
readOnly
checked={isDone}
type="checkbox"
/>
<div className="content">{content}</div>
<div className="date">{new Date(date).toLocaleDateString()}</div>
<button onClick={onClickDeleteButton}>삭제</button>
</div>
);
};
export default memo(ToDoItem);
객체타입을 갖고 있는 컴포넌트는 memo를 적용한다고 해서 최적화가 제대로 이뤄지지 않는다.
이럴때는 2가지 해결방법이 있다.
1. App 컴포넌트에서 함수 자체를 memoization한다.
2. ToDoItems 메모 함수안에 두번째 인수로 콜백함수를 줘 최적화 기능 커스텀한다.
import React from 'react';
import './ToDoItem.css';
import { memo } from 'react';
const ToDoItem = ({ id, isDone, content, date, onUpdate, onDelete }) => {
const onChangeCheckBox = () => {
// 인수로 id전달
onUpdate(id);
};
const onClickDeleteButton = () => {
onDelete(id);
};
return (
<div className="ToDoItem">
<input
// onClick이 아니라 onChange를 사용한 이유는 요소가 인풋요소이기때문
onChange={onChangeCheckBox}
readOnly
checked={isDone}
type="checkbox"
/>
<div className="content">{content}</div>
<div className="date">{new Date(date).toLocaleDateString()}</div>
<button onClick={onClickDeleteButton}>삭제</button>
</div>
);
};
export default memo(ToDoItem, (prevProps, nextProps) => {
// 반환값에 따라, props가 바뀌는지, 안바뀌었는지 판단
// T -> props가 바뀌지 않았다면 true를 반환 -> 리렌더링x
// F -> props가 바뀌었다면 false를 반환 -> 리렌더링o
if (prevProps.id !== nextProps.id) {
return false;
}
if (prevProps.isDone !== nextProps.isDone) {
return false;
}
if (prevProps.content !== nextProps.content) {
return false;
}
if (prevProps.date !== nextProps.date) {
return false;
}
// 4개의 값이 안바뀔때만 true를 반환
return true;
});
memoization처럼 성능을 최적화하거나 기능을 추가하는 방식으로, 기존 컴포넌트를 감싸 새로운 컴포넌트를 반환하는 함수를 고차 컴포넌트(Higher-Order Component, HOC) 라고 한다.
예를 들어, React.memo는 HOC의 대표적인 예로, 한 번 감싸기만 하면 props가 바뀌지 않는 한 컴포넌트를 다시 렌더링하지 않게 해준다.
HOC는 컴포넌트를 인자로 받아 기능을 추가한 컴포넌트를 반환하므로,
반복적으로 같은 기능을 넣지 않아도 되고, 한 번 호출로 재사용 가능한 기능을 손쉽게 부여할 수 있다는 장점이 있다.👍🏻
고차 컴포넌트(HOC) 관련 아티클
https://patterns-dev-kr.github.io/design-patterns/hoc-pattern/
HOC 패턴
📜 원문: patterns.dev - hoc pattern 종종 여러 컴포넌트에서 같은 로직을 사용해야 하는 경우가 있다. 이런 로직은 컴포넌트의 스타일시트를 설정하는 것일 수 있고. 권한을 요청하거나. 전역 상태를
patterns-dev-kr.github.io
🛡️ useCallback - 불필요한 함수 재생성 방지
import {useCallback} from react;
...
useCallback(() => {}, []);
- useCallback은 함수를 기억(memoization) 해두는 React Hook
- ()=>{} 는 함수(=콜백 함수), callback함수를 그대로 생성해 반환한다. 👉🏻 실제로 기억할 콜백 함수
- []는 의존성 배열(dependency array) 👉🏻 한 번만 만들고 다시는 안 바꾼다는 뜻(컴포넌트가 리렌더돼도 유지)
// delete button
// const onDelete = (targetId) => {
// dispatch({ type: 'DELETE', targetId });
// };
const onDelete useCallback((targetId) => {
dispatch({ type: 'DELETE', targetId });
}, []);
// 빈 배열을 통해 다시 생성하지 않도록 함
onDelete가 useCallback에 의해 마운트되었을때 딱 한번 생성된다. 그 이후 리렌더링이 된다고 해도 다시 생성되지 않도록 최적화된다. 나머지 코드들도 동일하게 작업하면 된다.
고차 컴포넌트를 통한 작업은 필요없어 졌다. callBack()함수를 사용했기때문이다.
1️⃣ 최적화를 언제해야 되는가?
리액트 최적화시 하나의 프로젝트를 거의 완성한 상태에서 최적화 작업
순서
1. 기능 구현 완료 ➡️ 2. 최적화 작업
2️⃣ 어떤 것들이 최적화대상이어야 되는가?
모든 것의 최적화 적용🙅🏻♀️
꼭❗ 최적화가 필요할거같은 연산들이나 함수들이나 컴포넌트들에만 최적화 적용
ex) 유저의 행동에 따라서 개수가 많아질 수 있는 것 컴포넌트
ex) 함수가 많아 무거운 컴포넌트
3️⃣
🔵useCallback vs useMemo 차이
항목 | useCallback | useMemo |
목적 | 함수를 기억하려고 씀 | 값(데이터)을 기억하려고 씀 |
기억하는 대상 | function (함수) | 계산 결과 value (숫자, 배열, 객체 등) |
언제 사용? | 1. 콜백 함수를 자식 컴포넌트에 넘길 때 2. 렌더링 최적화할 때 | 1. 계산 비용이 큰 연산을 캐싱할 때 2. 렌더링 때 다시 계산하지 않게 하고 싶을 때 |
형태 | const fn = useCallback(() => {}, [deps]); | const value = useMemo(() => 계산(), [deps]); |
비유 | "이 함수 그냥 기억해둬!" | "이 값 계산했으면 기억해둬!" |
아티클 "When to use useMemo, useCallback"
https://goongoguma.github.io/2021/04/26/When-to-useMemo-and-useCallback/
'🔷 React' 카테고리의 다른 글
[React] 최적화 🚀 (0) | 2025.04.24 |
---|---|
[React] useReducer (0) | 2025.04.23 |
[React] PROJ2. TODO LIST + useReducer로 바꿔보기 (0) | 2025.04.21 |
[React] 라이프사이클 (0) | 2025.04.09 |
[React] 카운터앱, State Lifting (4) | 2025.04.09 |