useMemo는 연산된 결과값(Value)을 캐싱하는 Hook이다.
복잡하고 비용이 많이 드는 계산을 할 때 사용한다.
예를 들어, 수만 개의 배열 데이터를 필터링하거나 정렬하는 로직이 컴포넌트 안에 있다면 단순한 UI 업데이트(ex. 모달 창 열기)로 인해 컴포넌트가 재렌더링될 때마다 이 무거운 정렬 로직이 다시 실행되어 성능이 크게 저하될 것이다. 이때 useMemo를 사용한다.
import React, { useMemo, useState } from 'react';
const ExpensiveComponent = ({ list }) => {
const [query, setQuery] = useState('');
// list가 변경될 때만 무거운 필터링 연산을 수행!
// query(검색어)가 변경될 때는 연산을 건너뛰고 캐싱된 값을 사용
const filteredList = useMemo(() => {
console.log("무거운 연산 중...");
return list.filter(item => item.includes('react'));
}, [list]);
return (
// JSX 생략...
);
};
useCallback은 '함수(Function)' 자체를 캐싱하는 Hook이다.
useCallback을 사용하면 의존성 배열의 값이 변하지 않는 한, 렌더링 사이클 사이에서 동일한 함수 인스턴스를 유지한다.자식 컴포넌트에 콜백 함수를 Props로 전달할 때 가장 유용하다.
자식 컴포넌트가 React.memo 등으로 최적화되어 있더라도, 부모 컴포넌트에서 매번 새로운 함수를 만들어 Props로 넘겨주면 자식은 Props가 변경되었다고 착각하여 불필요한 재렌더링을 하게 된다.
import React, { useState, useCallback } from 'react';
import ChildComponent from './ChildComponent';
const ParentComponent = () => {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// count가 변경될 때만 함수가 새로 생성
// text만 변경되어 재렌더링될 때는 기존 함수를 재사용하여 자식의 재렌더링을 방지
const handleCountUp = useCallback(() => {
setCount((prev) => prev + 1);
}, []);
return (
<div>
<input value={text} onChange={(e) => setText(e.target.value)} />
{/* 자식 컴포넌트의 불필요한 렌더링 방지 */}
<ChildComponent onCountUp={handleCountUp} />
</div>
);
};
useMemo와 useCallback은 의존성 배열 안의 값들이 이전과 같은지 비교하는 과정을 거친다. 연산 자체가 엄청 단순한데, 굳이 의존성 배열을 순회하며 비교하는 비용이 더 든다면 큰 손해를 보게 된다.