본문 바로가기

교육

#007 React useMemo : 성능 최적화의 정석

반응형

1. useMemo란? — 기본 개념

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

 

  • 의존성 배열([a, b])의 값이 변경될 때만 계산 함수 실행
  • 변경되지 않으면 이전 계산 결과 재사용 (메모이제이션)

 

주의: useMemo는 힌트(hint)일 뿐 보장 X

React는 메모리 압박 시 의존성 불변이라도 재계산 가능
순수 함수(동일 입력 → 동일 출력)만 사용해야 안전

 

2. 실무에서 흔한 3대 오용 사례

1. 단순 값 할당
오버헤드 > 이득
useMemo제거
2. 객체/배열 무분별 생성
참조 불일치로 리렌더 폭증
useMemo필수
3. 의존성 누락/과잉
stale closure / 불필요한 재계산
eslint-plugin-react-hooks

사례 1: 단순 문자열 생성 (비추천)

// ❌ 오용: 계산 비용이 낮음
const title = useMemo(() => `${menuName} - ${system}`, [menuName, system]);

// ✅ 개선: 일반 변수
const title = `${menuName} - ${system}`;

사례 2: 객체/배열 생성 (권장)

// ❌ 오용: 매 렌더링 시 새 객체 생성 → 자식 컴포넌트 불필요 리렌더
const contextValue = {
  user: currentUser,
  logout: handleLogout,
};

// ✅ 개선: useMemo로 참조 안정화
const contextValue = useMemo(() => ({
  user: currentUser,
  logout: handleLogout,
}), [currentUser, handleLogout]);

사례 3: 의존성 실수 (위험)

// ❌ 오용: currentUser 누락 → stale closure
const userInfo = useMemo(() => ({
  id: currentUser.id, // currentUser 변경 시 갱신 안 됨!
  displayName: getDisplayName(currentUser),
}), []); // ⚠️ 의존성 누락

// ✅ 개선: 모든 의존성 명시
const userInfo = useMemo(() => ({
  id: currentUser.id,
  displayName: getDisplayName(currentUser),
}), [currentUser]); // ✅

 

부록: useMemo vs useCallback 비교

 

 
USEMEMO
USECALLBACK
용도
값 메모이제이션
함수 메모이제이션
사용법
useMemo(() => value, deps)
useCallback(() => fn, deps)
동등 표현
useMemo(() => fn, deps)
useMemo(() => fn, deps)
주요 시나리오
객체/배열/계산된 값
이벤트 핸들러, 콜백 전달

 

핵심:

  • useCallback(fn, deps) ≡ useMemo(() => fn, deps)
  • 객체 생성 → useMemo
  • 함수 생성 → useCallback

 

useMemo
계산된 값의 메모이제이션
캐시, 성능 최적화, 참조 안정화
useRef
값 저장소(리렌더 X)
DOM 참조, 이전 값, 타이머
useState
업데이트 가능한 상태
사용자 입력, UI 상태
useCallback
함수 메모이제이션
콜백 전달, 자식 컴포넌트 최적화

 

React에서의 메모이제이션 — useMemo

React는 컴포넌트가 리렌더될 때마다 모든 코드를 다시 실행합니다.
문제는 계산 비용이 큰 로직이 매번 재실행된다는 것!

 

❌ 문제 상황

function UserList({ users }) {
  // 🚨 매 리렌더링 시 10,000건 정렬 → 100ms 지연!
  const sortedUsers = users
    .filter(u => u.isActive)
    .sort((a, b) => a.name.localeCompare(b.name));

  return (
    <ul>
      {sortedUsers.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}

✅ 해결: useMemo로 메모이제이션

function UserList({ users }) {
  // ✅ users 변경 시만 재계산
  const sortedUsers = useMemo(() => {
    console.log("정렬 실행!"); // users 변경 시만 출력
    return users
      .filter(u => u.isActive)
      .sort((a, b) => a.name.localeCompare(b.name));
  }, [users]); // 의존성: users

  return (
    <ul>
      {sortedUsers.map(user => <li key={user.id}>{user.name}</li>)}
    </ul>
  );
}

 

 

동작 원리

 

 

메모이제이션이 효과적인 상황

대용량 데이터 처리
1,000+ 건 필터링/정렬
100ms → 2ms
복잡한 객체 생성
{ user, permissions, config }
자식 리렌더 방지
비용 높은 계산
피보나치, deep clone
응답성 향상
단순 값 할당
const title = name + '님'
오버헤드 > 이득

 

 

 

 

 

 

반응형