
React – 상태 관리와 Hooks – 기본 Hooks – 3 – useMemo와 useCallback 최적화 목적과 차이
안녕하세요! 😊
이번 시간에는 React에서 성능 최적화에 중요한 역할을 하는 두 가지 훅,
바로 useMemo
와 useCallback
에 대해 알아보겠습니다!
이 둘은 이름도 비슷하고 사용법도 비슷해서 헷갈리기 쉬운데요,
“계산 결과를 기억할 건지”, **”함수를 기억할 건지”**에 따라 구분하면 훨씬 이해가 쉬워요.
그럼 한 번, 각각 어떤 상황에서 사용하고 어떤 차이가 있는지, 실제 예시와 함께 쏙쏙 이해해볼까요?
1. 왜 최적화가 필요할까요?
React는 상태나 props가 바뀌면 컴포넌트를 리렌더링해요.
이때 렌더링 안에서 무거운 연산이 계속 반복되거나, 불필요한 함수 재생성이 발생하면 성능이 나빠질 수 있어요.
예를 들면…
매 렌더링마다 for문 100만 번 돌리면…
🐢 화면도 느려지고 사용자도 답답해지겠죠?
바로 이런 상황을 방지하기 위해 사용하는 게 useMemo
와 useCallback
입니다!
2. useMemo란?
👉 계산된 값을 기억(Memoization)
useMemo
는 값(value) 을 캐싱해서, 의존성이 바뀌지 않으면 이전 계산 결과를 재사용하게 해줘요.
const memoizedValue = useMemo(() => {
return 복잡한계산(값);
}, [값]);
예제: 무거운 계산
const expensiveCalc = (num) => {
console.log('무거운 계산 중...');
let result = 0;
for (let i = 0; i < 100000000; i++) {
result += num;
}
return result;
};
function MyComponent({ number }) {
const result = useMemo(() => expensiveCalc(number), [number]);
return <div>계산 결과: {result}</div>;
}
number
가 변경될 때만expensiveCalc
가 실행되고,- 동일하면 계산 결과를 기억해서 재사용해요! 성능 굿굿!
3. useCallback이란?
👉 함수를 기억(Memoization)
useCallback
은 함수(function) 를 캐싱해서, 의존성이 같으면 이전 함수를 그대로 재사용해요.
const memoizedFn = useCallback(() => {
함수내용;
}, [의존성]);
예제: 자식 컴포넌트에 함수 전달
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log('버튼 클릭!');
}, []); // 한 번만 생성됨
return (
<>
<Child onClick={handleClick} />
<button onClick={() => setCount(count + 1)}>증가</button>
</>
);
}
function Child({ onClick }) {
console.log('Child 렌더링!');
return <button onClick={onClick}>자식 버튼</button>;
}
🔍 여기서 만약 useCallback
을 쓰지 않으면 handleClick
함수가
Parent
가 렌더링될 때마다 새로 생성되고,
그걸 받는 Child
도 불필요하게 리렌더링됩니다!
4. useMemo vs useCallback 비교
항목 | useMemo | useCallback |
---|---|---|
기억하는 대상 | 계산된 값 | 선언된 함수 |
사용 목적 | 계산 결과 재사용 | 동일 함수 객체 재사용 |
반환값 | 값 (예: 숫자, 배열 등) | 함수 |
사용 시기 | 무거운 연산 결과를 저장하고 싶을 때 | props로 전달되는 함수를 재사용하고 싶을 때 |
자주 쓰는 곳 | 리스트 필터링, 합계 계산 등 | 자식 컴포넌트에 함수 props 넘길 때 |
5. 실전 예제: 둘 다 활용
function TodoApp({ todos }) {
const [filter, setFilter] = useState('all');
const filteredTodos = useMemo(() => {
console.log('필터링 중...');
return todos.filter(todo => {
if (filter === 'completed') return todo.done;
if (filter === 'active') return !todo.done;
return true;
});
}, [todos, filter]);
const toggleTodo = useCallback((id) => {
console.log(`할 일 ${id} 토글`);
}, []);
return (
<div>
<select onChange={e => setFilter(e.target.value)}>
<option value="all">전체</option>
<option value="active">활성</option>
<option value="completed">완료</option>
</select>
{filteredTodos.map(todo => (
<TodoItem key={todo.id} todo={todo} onToggle={toggleTodo} />
))}
</div>
);
}
useMemo
: 매 렌더링마다todos.filter()
를 반복하지 않도록 필터링 결과를 저장useCallback
:toggleTodo
함수가 변하지 않아 자식 컴포넌트 리렌더링 방지
6. 주의해야 할 점
주의사항 | 설명 |
---|---|
무분별한 사용 ❌ | 간단한 연산이나 함수에 useMemo , useCallback 을 사용하면 오히려 비용이 증가해요 |
의존성 정확히 지정 | [값] 이 누락되면 오래된 값이 캐싱될 수 있음 |
필요할 때만 사용 | 실제 렌더링에 성능 문제가 있을 때 도입하는 것이 좋아요 |
7. 비유로 이해해볼까요?
-
useMemo
는 “계산기 기억 기능”!
→ 똑같은 식이면 또 계산하지 않고, 예전 결과 꺼내줌! -
useCallback
은 “함수 저장소”!
→ 똑같은 기능의 함수면, 새로 만들지 않고 예전 함수 재사용!
8. 요약 정리
훅 | 기억 대상 | 사용 목적 | 리렌더 영향 |
---|---|---|---|
useMemo |
값 | 무거운 계산 최적화 | 없음 |
useCallback |
함수 | props 최적화 | 없음 |
👉 둘 다 렌더링을 막는 게 아니라,
불필요한 계산 또는 함수 재생성을 줄여주는 훅이에요!
마무리하며
성능 최적화는 너무 일찍 걱정할 필요는 없지만,
useMemo와 useCallback은 꼭 알아야 할 기본기 중 하나랍니다!
특히 컴포넌트가 많아지거나, 렌더링이 자주 발생하거나,
props로 함수를 자주 넘기는 구조라면 꼭 활용해보세요.
🌱 오늘 배운 걸 요약하면?
🔹
useMemo
: 무거운 계산 결과를 기억해서 반복하지 않기
🔹useCallback
: 함수를 기억해서 자식 컴포넌트 리렌더 방지
다음 시간엔 useContext
와 상태 공유 방식에 대해 알아보며
더 구조적인 리액트 앱 설계에 한 걸음 더 나아가볼게요! 😊💪