useMemo
와 useCallback
은 리렌더링 시 불필요한 계산과 함수 생성을 방지해서 성능을 최적화하는 데 사용돼.
TypeScript로 사용할 때는 반환 타입과 의존성 배열에 특히 신경 써야 해.
useMemo
)import { useMemo, useState } from 'react';
const ExpensiveCalculation = () => {
const [count, setCount] = useState(0);
const double = useMemo<number>(() => {
console.log('계산 중...');
return count * 2;
}, [count]); // count가 바뀔 때만 재계산됨
return (
<>
<p>두 배: {double}</p>
<button onClick={() => setCount(count + 1)}>증가</button>
</>
);
};
✅ useMemo<number>처럼 반환 타입을 명시할 수 있어. 복잡한 계산일수록 캐싱 효과가 커져.
useCallback
)import { useCallback, useState } from 'react';
const CallbackExample = () => {
const [count, setCount] = useState(0);
const handleClick = useCallback((): void => {
alert(`현재 카운트: ${count}`);
}, [count]);
return <button onClick={handleClick}>알림</button>;
};
✅ useCallback은 함수를 메모이제이션해서, 불필요한 재생성을 방지해. 자식 컴포넌트에 함수를 prop으로 넘길 때 특히 중요해.
const [value, setValue] = useState(1);
const memoizedValue = useMemo(() => {
return value * 10;
}, [value]); // ✅ value 타입은 number
// 잘못된 예: 의존성 빠뜨림
// useMemo(() => value * 10, []); ❌ value가 바뀌어도 캐시가 갱신되지 않음
✅ TypeScript는 의존성 배열을 정확히 분석하지 못하므로 ESLint의 react-hooks/exhaustive-deps 플러그인을 쓰는 것이 좋아.
type User = {
id: number;
name: string;
};
const UserList = ({ users }: { users: User[] }) => {
const sortedUsers = useMemo(() => {
return [...users].sort((a, b) => a.name.localeCompare(b.name));
}, [users]);
return (
<ul>
{sortedUsers.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
};
✅ useMemo는 정렬, 필터링 등 연산 비용이 큰 작업을 캐싱하는 데 유용해.
중요한 건 의존성 배열에
users
처럼 정확한 참조를 넣는 것.