FEInterview Prep

react · high priority

React 최적화 원리 — 리렌더 규칙, 메모이즈의 비용, React Compiler

`memo`/`useMemo`/`useCallback` 의 진짜 비용과 "언제 안 쓰는가"

advanced 난이도5시간토스카카오네이버당근배민라인
시작 전
이해도
매우 낮음

학습 개요

탄생 배경

쉬운 설명

복잡한 개념을 실생활 비유로 설명합니다.

단체 사진 다시 찍기

한 명이 자세를 바꿀 때마다 모두 다시 찍어 두는 것이 React 의 기본 동작입니다. `React.memo` 는 "내 옷이 그대로면 다시 찍지 마세요" 라는 표지판이고, `useMemo`/`useCallback` 은 "내가 들고 있는 손가방·미소 의 *모양과 동일성* 을 유지" 하는 약속입니다. Compiler 는 사진사가 자동으로 그런 표지판을 들어주는 도구이고, 우선순위(`useTransition`) 는 "VIP 가 들어오면 내 사진은 미루고 그쪽부터" 라는 다른 축의 도구입니다.

핵심 개념

리렌더가 발생하는 정확한 조건
원인발동 조건메모이즈와의 관계
자기 state 변경setState 가 이전과 다른 값을 푸시memo 로도 못 막는다 — 본인 렌더는 본인 책임
받은 props 변경React.memo 적용 시에만 비교가 의미 있음얕은 비교 — 객체/배열/함수는 참조 동일성이 핵심
부모 렌더*기본값* — props 가 같아도 자식 렌더React.memo 가 필요한 유일한 이유
Context value 변경구독한 모든 소비자memo 로는 못 막는다 — Context 분리 또는 selector 패턴

"props 가 안 바뀌면 자식이 안 그려진다" 는 잘못된 모델

React 의 *기본 동작* 은 "부모 렌더 → 자식도 렌더" 입니다. props 가 같아도, 함수가 같아도, 자식은 다시 렌더 함수가 호출됩니다. *이 기본을 깨려면* React.memo 가 필요하고, memo 가 의미 있으려면 props 의 *참조 동일성* 까지 챙겨야 합니다.

먼저 출력 순서를 예측해보세요
예측 문제tsx
1function Parent() {
2 const [n, setN] = useState(0);
3 return (
4 <>
5 <button onClick={() => setN(n + 1)}>{n}</button>
6 <Child name="알리스" /> {/* 같은 문자열 props */}
7 </>
8 );
9}
10const Child = ({ name }: { name: string }) => {
11 console.log('Child render');
12 return <div>{name}</div>;
13};
14// 버튼을 5번 누르면 'Child render' 가 몇 번 찍히는가?

정답

6 번 (초기 마운트 1 + 클릭 5).

해설

props 가 같아도 부모가 렌더되면 자식도 렌더 함수가 호출됩니다. ChildReact.memo(Child) 로 감싸면 props 가 동일하므로 5 번의 클릭은 자식 렌더를 건너뛰고 콘솔에는 1 번만 찍힙니다.

흔한 오답

  • "문자열 같은 원시값 props 면 자동으로 안 그려진다" — 아니다. 부모 렌더는 자식 렌더를 *기본적으로* 끌고 온다.
  • "memo 안 붙여도 React 가 알아서 비교한다" — 아니다. 비교 자체가 비용이라 React 는 명시 옵트인이다 (Compiler 가 자동화하기 전까지는).

실무 적용

어떤 상황에서 사용하는가

대시보드 페이지에서 토글 한 번에 200 개 이상의 자식 컴포넌트가 렌더되어 INP 가 300 ms 를 넘었다는 RUM 경고가 들어왔다.

어떻게 적용하는가

(1) Profiler 로 토글 → 어떤 트리가 리렌더되는지 캡처. (2) 광범위 Context 가 원인이면 *영향 범위가 작은 슬라이스* 로 Context 분리 또는 외부 스토어 + selector 도입. (3) memo 된 자식인데도 깨지는 노드는 props 의 인라인 객체/함수를 useMemo/useCallback 으로 안정화. (4) 정렬·필터 같은 큰 계산은 `useMemo` 로 메모이즈하거나 Web Worker 로 이동. (5) 같은 토글 흐름을 React Compiler 가 적용된 빌드에서 다시 측정해 손으로 쓴 메모이즈가 여전히 가치가 있는지 검증.

흔한 실수와 안티패턴

  • 모든 컴포넌트에 `React.memo` 를 일괄 적용 — 비교 비용만 늘리고 이득은 핫패스에만 있다.
  • deps 배열을 빈 배열로 두고 안에서 최신 state 를 참조 — stale closure 버그.
  • `useCallback` 의 함수가 큰 객체를 캡처해 메모리 누수.
  • Context value 를 매 렌더 새 객체로 만들면서 Provider 위치만 옮김.
  • Compiler 적용 후 손 메모이즈를 *통째로* 지우는 광범위 리팩터링 — 회귀 시 원인 추적이 어려워진다.

흔한 오해

오해

"`React.memo` 는 항상 좋다."

교정

비교 비용 + 메모리 보존 비용이 따라온다. 핫패스에만.

왜 중요

props 가 자주 바뀌는 컴포넌트에는 비교만 더 늘어 손해다. 측정 후 적용해야 한다.

오해

"`useCallback` 은 함수 컴포넌트의 모든 함수에 붙여야 한다."

교정

*memo 된 자식의 props* 로 들어가거나 *외부 시스템에 등록* 되는 함수에만 의미가 있다.

왜 중요

단순한 onClick 핸들러를 useCallback 으로 감싸도 자식이 memo 가 아니면 어차피 자식은 매번 렌더된다. 비용만 늘 뿐.

오해

"React Compiler 가 나오면 메모이즈는 다 사라진다."

교정

Compiler 가 못 보는 경계(외부 라이브러리, Worker, Context value 의도 표현 등) 에 사람이 쓰는 자리가 남는다.

왜 중요

Compiler 는 사용자 코드의 정적 패턴만 메모이즈한다. React 트리 바깥과의 참조 동일성, 명시적 의도 표현은 여전히 사람의 몫이다.

면접 질문

심화토스카카오네이버배민

답변 방향 힌트

"부모가 그려지면 자식도 그려진다" 가 *기본* 임을 깔고 시작하세요.

반드시 언급할 키워드

  • 자기 state 변경 — 본인 책임, memo 로 못 막음
  • 받은 props 변경 — `React.memo` 의 얕은 비교 대상
  • 부모 렌더 — `React.memo` 가 *유일한* 차단 도구
  • Context value 변경 — Context 분리 / selector / 외부 스토어
  • 얕은 비교를 깨는 원흉은 인라인 객체/배열/함수

예상 꼬리 질문

  • Context value 객체를 useMemo 로 감싸면 광범위 리렌더가 해결되나요?
  • children prop 이 매 렌더 새 React 엘리먼트라는 사실은 어떻게 활용할 수 있나요?

자기 점검

`React.memo` 가 *유일하게* 막을 수 있는 리렌더 원인은 무엇인가?

기대 키워드

부모 렌더같은 props얕은 비교기본 동작

자주 하는 오해

"props 변경 자체를 막는다" 는 틀렸다 — props 가 진짜로 바뀌면 당연히 다시 그려진다.

`useCallback` 이 무용지물인 대표 케이스 한 가지를 한 문장으로 답하라.

기대 키워드

memo 안 된 자식어차피 부모 따라 렌더안정화 이득 없음

자주 하는 오해

"모든 함수는 useCallback 에 감싸야 한다" 는 잘못된 일반화.

React Compiler 가 자동 메모이즈해도 *사람이 직접* 써야 하는 상황 한 가지는?

기대 키워드

외부 라이브러리/네이티브 경계Web Worker 메시지참조 동일성컴파일러가 못 보는 곳

자주 하는 오해

"Compiler 가 나오면 메모이즈가 0 으로 줄어든다" 는 과장.

학습 자료