반응형

React 앱이 느려지는 주요 원인은 대부분 “불필요한 리렌더링”입니다.
컴포넌트가 재렌더링될 때마다 함수, 객체, 배열이 새로 생성되기 때문이죠.
이 문제를 해결하기 위한 세 가지 Hook:
🧠 useMemo, ⚙️ useCallback, 📦 useRef
이 세 가지를 제대로 알면 성능 최적화 + 안정적인 상태 관리가 동시에 가능합니다 👇
🧠 1. useMemo – 계산 결과를 메모이징
복잡한 계산의 결과를 기억해두고, 의존값이 변할 때만 다시 계산.
import { useMemo, useState } from "react";
function ExpensiveComponent({ items }: { items: number[] }) {
const [count, setCount] = useState(0);
const total = useMemo(() => {
console.log("복잡한 계산 실행...");
return items.reduce((a, b) => a + b, 0);
}, [items]);
return (
<div>
<p>합계: {total}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}
📌 items가 변경되지 않는 한 reduce()는 다시 실행되지 않음
→ 렌더링 시 불필요한 연산 절감
✅ 사용 시점
- 무거운 연산 (정렬, 필터, 계산 등)
- 동일한 입력에 동일한 결과 반환되는 함수
⚙️ 2. useCallback – 함수 재생성 방지
함수를 메모이징해서, 불필요한 자식 컴포넌트 리렌더링 방지.
import { useCallback, useState } from "react";
function Button({ onClick }: { onClick: () => void }) {
console.log("버튼 렌더링");
return <button onClick={onClick}>클릭</button>;
}
function App() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount((prev) => prev + 1);
}, []);
return (
<>
<p>Count: {count}</p>
<Button onClick={handleClick} />
</>
);
}
📌 일반 함수로 작성하면 매 렌더마다 새로운 함수 객체가 생성되어
Button이 매번 리렌더링됨.
→ useCallback으로 방지 ✅
✅ 사용 시점
- 자식 컴포넌트에 콜백 전달 시
- 이벤트 핸들러, useEffect 의존성 배열에 들어갈 때
📦 3. useRef – 렌더링에 영향 없는 데이터 저장소
DOM 접근, 이전 값 저장, 변경값 추적 등에 사용.
import { useRef, useEffect, useState } from "react";
function Timer() {
const [count, setCount] = useState(0);
const intervalRef = useRef<number | null>(null);
useEffect(() => {
intervalRef.current = window.setInterval(() => {
setCount((c) => c + 1);
}, 1000);
return () => clearInterval(intervalRef.current!);
}, []);
return <p>{count}초 경과</p>;
}
📌 useRef는 렌더링을 일으키지 않음
📌 변경해도 UI 리렌더링이 발생하지 않아 성능 부담 없음
✅ 사용 시점
- DOM 요소 접근 (ref.current.focus())
- setInterval, setTimeout 관리
- 이전 값 저장 (prevValue.current = value)
⚡ 4. useMemo vs useCallback 차이
| Hook | 메모이징 대상 | 반환값 | 주 사용 사례 |
| useMemo | 계산 결과 | 값(value) | 복잡한 계산 최적화 |
| useCallback | 함수 | 함수(fn) | 이벤트 핸들러 캐싱 |
📌 useCallback(fn, deps) 은 사실상 useMemo(() => fn, deps)와 동일
🧩 5. 세 Hook을 함께 사용하는 실무 예시
import { useMemo, useCallback, useRef, useState } from "react";
function SearchList({ list }: { list: string[] }) {
const [query, setQuery] = useState("");
const inputRef = useRef<HTMLInputElement>(null);
const filtered = useMemo(
() => list.filter((item) => item.includes(query)),
[list, query]
);
const focusInput = useCallback(() => {
inputRef.current?.focus();
}, []);
return (
<div>
<input ref={inputRef} value={query} onChange={(e) => setQuery(e.target.value)} />
<button onClick={focusInput}>검색창 포커스</button>
<ul>
{filtered.map((item) => (
<li key={item}>{item}</li>
))}
</ul>
</div>
);
}
📌 3대 Hook 종합 예제
- useMemo → 필터링 최적화
- useCallback → 버튼 클릭 이벤트 캐싱
- useRef → DOM 접근
🧠 6. 성능 최적화 실무 팁
✅ 무조건 감싸지 말기 (불필요한 메모이징은 오히려 손해)
✅ useMemo는 “비싼 연산”만 캐싱
✅ useCallback은 “자식 리렌더링 방지”에만 사용
✅ useRef는 상태 대신 “렌더링 영향 없는 값” 저장
📝 마무리 정리
- useMemo → 연산 결과 캐싱
- useCallback → 함수 캐싱
- useRef → DOM 및 불변 데이터 저장
👉 이 세 가지를 적절히 쓰면
불필요한 렌더링을 60~80% 줄이고, UX 반응성도 크게 개선됩니다 ⚡
반응형
'React, Vue' 카테고리의 다른 글
| React SEO ✅ 메타태그, Open Graph, sitemap (0) | 2025.12.16 |
|---|---|
| 웹 성능 최적화 ✅ 코드 스플리팅, Lazy Loading, 이미지 최적화 (0) | 2025.12.13 |
| React 애니메이션 ✅ Framer Motion으로 부드럽고 감성적인 UI 만들기 (0) | 2025.12.07 |
| Next.js 13+ App Router ✅ Layout, Page, Loading, Metadata (0) | 2025.11.19 |
| Next.js 렌더링 ✅ CSR, SSR, SSG, ISR 차이와 선택 (0) | 2025.11.16 |