⭐ 6편 — 리액트 리렌더링과 성능 최적화 완전 정복: React.memo, useCallback, useMemo 제대로 쓰는 법
리액트를 제대로 배우다 보면 언젠가 반드시 이런 고민이 생깁니다.
“왜 이 컴포넌트가 계속 리렌더링되지?”
“state를 바꾸면 관계 없는 컴포넌트까지 다시 그려지는 이유는?”
“최적화는 언제, 어떻게 하는 게 맞는 걸까?”
리렌더링과 최적화에 대한 이해가 부족하면
프로젝트 규모가 조금만 커져도 성능 저하가 금방 드러납니다.
저 역시 초기에 최적화를 잘못 이해해서 필요 없는 곳에 useMemo를 남발했고,
오히려 코드만 복잡해진 경험이 있습니다.
이번 글에서는 “언제 최적화를 해야 하는지”부터
각 도구(React.memo / useCallback / useMemo)를 어떻게 써야 하는지까지
아주 현실적으로 정리해드릴게요.
✅ 1. 리액트 리렌더링의 기본 원리
리액트는 상태(state)나 props가 바뀌면 해당 컴포넌트를 다시 렌더링합니다.
그러면 자연스럽게 이런 현상이 발생합니다.
✔ 부모가 리렌더링되면
→ 자식 컴포넌트도 기본적으로 함께 리렌더링됨
즉, “부모 → 자식 체인” 전체에 영향을 미치는 구조입니다.
이 구조 때문에 최적화를 고려하게 되는 것입니다.
🔍 리렌더링이 나쁜 게 아니다
많은 초보자들이 리렌더링 자체를 문제로 생각하지만,
리렌더링은 리액트의 정상적인 동작입니다.
문제는 불필요한 리렌더링입니다.
값이 변하지 않았는데도
컴포넌트가 계속 리렌더링된다면
그때 우리는 최적화를 고민해야 합니다.
✅ 2. React.memo — “props가 바뀌지 않으면 다시 렌더링하지 마!”
React.memo는 컴포넌트를 메모이제이션하는 기능입니다.
export default React.memo(MyComponent);
✔ 동작 방식
부모가 리렌더링되더라도
props가 바뀌지 않았다면 렌더링을 건너뛰는 방식입니다.
✔ 언제 쓰는가?
리스트 렌더링
자주 바뀌지 않는 UI
무거운 연산이 있는 자식 컴포넌트
❗ React.memo를 쓸 때 주의할 점
props 값이 객체/배열/함수라면 깊은 비교가 아니기 때문에
값이 같아 보여도 변경된 것으로 간주됩니다.
예시:
<MyChild list={[]} />
부모가 리렌더링될 때마다 새로운 배열 []이 만들어져
React.memo가 있어도 자식이 리렌더링됩니다.
이 문제는 아래 훅(useCallback, useMemo)에서 해결합니다.
✅ 3. useCallback — “함수 재생성 막기”
부모 컴포넌트가 리렌더링될 때마다
컴포넌트 내부에서 새 함수가 생성됩니다.
이때 그 함수가 자식에게 props로 전달되면
React.memo가 있어도 자식이 리렌더링됩니다.
이 문제를 해결하는 훅이 useCallback입니다.
const onClick = useCallback(() => {
console.log("clicked");
}, []);
✔ 언제 쓰는가?
자식에게 콜백 함수를 props로 넘길 때
React.memo와 함께 사용할 때
이벤트 핸들러 재생성 방지
🔍 useCallback 이해 핵심 포인트
useCallback은 “함수를 메모이제이션하는 도구”입니다.
즉, 의존성 배열이 바뀌지 않는 한
매번 같은 함수 인스턴스를 재사용합니다.
❗ useCallback도 남용하면 안 된다
함수를 저장하고 비교하는 비용이 있기 때문에
모든 함수에 useCallback을 쓰면 성능이 오히려 떨어집니다.
✔ 자식에게 함수 넘길 때
✔ 메모이제이션된 컴포넌트에서만
사용하는 것이 가장 좋습니다.
✅ 4. useMemo — “연산 비용이 큰 값을 캐싱”
useMemo는
컴포넌트가 렌더링될 때 매번 다시 계산하는 비용이 큰 연산을
메모리에서 재사용하도록 하는 기능입니다.
const result = useMemo(() => heavyWork(), [value]);
✔ 언제 쓰는가?
정렬, 필터링 같은 무거운 연산
리스트 계산
객체/배열을 props로 자식에게 넘길 때
🔍 useMemo의 핵심 포인트
useMemo는 값(value)의 메모이제이션이고
useCallback은 함수(function)의 메모이제이션입니다.
둘의 동작은 거의 유사하지만 목적은 다릅니다.
🔥 실전 예제로 이해하기 (가장 많이 쓰는 패턴)
아래는 실제 회사 프로젝트에서도 흔히 사용되는 패턴입니다.
✔ 자식 컴포넌트 최적화 패턴
const Child = React.memo(({ onClick, item }) => {
console.log("child render");
return <button onClick={onClick}>{item}</button>;
});
function Parent() {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
const item = useMemo(() => "메모된 값", []);
return (
<>
<Child onClick={handleClick} item={item} />
<button onClick={() => setCount(count + 1)}>부모 리렌더</button>
</>
);
}
✔ 실행 흐름
부모의 count가 바뀌어도
handleClick, item이 메모이제이션되어 있기 때문에
자식 컴포넌트는 리렌더링되지 않는다
이게 바로 최적화의 핵심 패턴입니다.
🚨 초보자가 성능 최적화에서 자주 하는 실수 4가지
❌ 1) useCallback/useMemo를 남용
모든 곳에 쓰면 오히려 성능이 떨어집니다.
❌ 2) React.memo만 쓰면 해결된다고 착각
props가 객체/배열/함수라면 memo가 작동하지 않습니다.
❌ 3) “리렌더링 자체”를 문제로 봄
리렌더링은 자연스러운 동작입니다.
문제는 “불필요한 리렌더링”입니다.
❌ 4) 언제 최적화를 해야 하는지 기준이 없음
성능 문제가 실제로 발생할 때, 또는
리스트/무거운 연산/자식 컴포넌트 의존성 문제에서만 최적화를 고려해야 합니다.
✨ 마무리: 최적화는 “필요한 곳에만 정확하게”
React.memo, useCallback, useMemo는
리액트 고급 개발자의 기본 도구입니다.
하지만 중요한 건
✔ 필요한 곳에만,
✔ 정확한 시점에,
✔ 이유가 있을 때만
사용해야 한다는 점입니다.
이 원칙을 지키면
프로젝트의 성능은 훨씬 안정적이고 재사용성이 높아집니다.
'리액트' 카테고리의 다른 글
| ⭐ 10편 — 리액트에서 비동기 처리 완전 정리: fetch, axios, async/await, useEffect 패턴까지 실전 가이드 (0) | 2025.11.25 |
|---|---|
| ⭐ 9편 — 리액트 상태 관리 기본기: Context API vs Redux 실전 비교, 초보자도 알 수 있게 정리 (0) | 2025.11.25 |
| ⭐ 7편 — React Router 완전 정복: 페이지 이동부터 동적·중첩 라우팅까지 실전 가이드 (0) | 2025.11.25 |
| ⭐ 8편 — 리액트 리스트 렌더링 완전 정복: key가 중요한 이유와 성능 문제까지 한 번에 이해하기 (0) | 2025.11.25 |
| ⭐ 5편 — useRef 완전 정복: DOM 제어부터 값 저장까지 실무에서 자주 쓰는 패턴 총정리 (0) | 2025.11.25 |
| ⭐ 4편 — 리액트 핵심 Hook 완전 정복: useState와 useEffect 제대로 쓰는 법 (0) | 2025.11.25 |
| ⭐ 3편 — 초보자 80%가 헷갈리는 Props vs State: 완벽하게 이해하는 쉬운 설명 (0) | 2025.11.25 |
| ⭐ 2편 — 리액트 컴포넌트 구조 완전 이해하기: 초보자는 몰라서 헤매는 핵심 개념 정리 (0) | 2025.11.25 |