테마
React DevTools 프로파일링
React Developer Tools의 Components 탭과 Profiler 탭을 활용하여 컴포넌트 트리를 검사하고, 렌더링 성능을 프로파일링하며, 불필요한 리렌더링을 감지하고 React.memo, useMemo, useCallback으로 최적화하는 방법을 학습한다.
학습 목표
- React Developer Tools의 Components 탭에서 컴포넌트 트리, Props, State를 검사할 수 있다
- Profiler 탭에서 렌더링 성능을 기록하고 분석할 수 있다
- 불필요한 리렌더링의 원인을 식별하고 진단할 수 있다
- React.memo, useMemo, useCallback을 적절히 활용하여 렌더링 성능을 최적화할 수 있다
1. React Developer Tools 소개
React Developer Tools(이하 React DevTools)는 React 애플리케이션을 개발하고 디버깅할 때 사용되는 브라우저 확장 프로그램이다. Chrome과 Firefox에서 사용할 수 있으며, React 컴포넌트를 직접 검사하고 성능 문제를 식별하는 데 필수적인 도구다.
1.1 설치 방법
1. Chrome 웹 스토어에서 "React Developer Tools" 검색
2. 확장 프로그램 설치
3. React 애플리케이션을 개발 모드로 실행
4. DevTools(F12) 열기 -> "Components"와 "Profiler" 탭 확인주의: 프로덕션 빌드에서는 프로파일링 정보가 제거되므로, --profile 플래그로 빌드하거나 개발 모드에서 분석해야 한다.
1.2 두 가지 핵심 탭
| 탭 | 아이콘 | 목적 |
|---|---|---|
| Components | React 로고 | 컴포넌트 트리, Props, State, Hooks 검사 |
| Profiler | React 로고 | 렌더링 성능 기록 및 분석 |
2. Components 탭
2.1 컴포넌트 트리 검사
Components 탭을 열면 현재 페이지의 React 컴포넌트 트리가 DOM 트리와 유사한 형태로 표시된다. 일반 Elements 탭이 HTML 구조를 보여준다면, Components 탭은 React 컴포넌트 계층 구조를 보여준다. 예를 들어 App > RouterProvider > Layout > Header > Navigation 같은 중첩 구조가 시각적으로 표시된다.
2.2 Props와 State 확인
컴포넌트를 선택하면 오른쪽 패널에서 해당 컴포넌트의 정보를 확인할 수 있다.
- Props: 부모로부터 전달받은 속성값
- State: 컴포넌트 내부의 상태값 (useState, useReducer)
- Hooks: 사용 중인 훅의 현재 값 (useMemo, useCallback 등)
- Rendered by: 이 컴포넌트를 렌더링한 부모 컴포넌트
2.3 실시간 편집
Components 탭에서는 Props와 State 값을 직접 수정할 수 있다. 수정하면 즉시 화면에 반영되므로, 특정 데이터 상태에서의 렌더링을 빠르게 확인하고 디버깅할 수 있다.
2.4 요소 선택 도구
DevTools 왼쪽 상단의 선택 아이콘을 클릭한 뒤 페이지의 요소를 클릭하면, 해당 요소를 렌더링하는 React 컴포넌트가 Components 탭에서 자동으로 선택된다.
3. Profiler 탭
Profiler 탭은 React 컴포넌트의 렌더링 빈도와 비용을 측정하는 도구다. 어떤 컴포넌트가 왜 렌더링되었는지, 얼마나 시간이 걸렸는지를 정확히 파악할 수 있다.
3.1 프로파일링 실행
1. Profiler 탭 선택
2. 파란색 녹화(Record) 버튼 클릭
3. 애플리케이션에서 상호작용 수행 (클릭, 입력, 페이지 전환 등)
4. 빨간색 정지(Stop) 버튼 클릭
5. 결과 분석3.2 세 가지 뷰 모드
Flame Chart (화염 차트)
- 각 렌더링 커밋(commit)별로 컴포넌트 트리를 보여준다
- 막대의 너비는 렌더링에 소요된 시간을 나타낸다
- 색상: 노란색/주황색은 비용이 높은 렌더링, 파란색은 빠른 렌더링, 회색은 렌더링되지 않음
Ranked Chart (순위 차트)
- 렌더링 시간이 가장 오래 걸린 컴포넌트부터 내림차순으로 정렬한다
- 어떤 컴포넌트가 성능 병목인지 한눈에 파악할 수 있다
Timeline (타임라인)
- 전체 기록 기간 동안의 렌더링 커밋을 시간축으로 표시한다
- 각 커밋의 지속 시간과 발생 시점을 확인할 수 있다
3.3 "Why did this render?" 기능
React DevTools 설정(톱니바퀴 아이콘)에서 "Record why each component rendered while profiling" 옵션을 활성화하면, 각 컴포넌트가 렌더링된 이유를 확인할 수 있다.
렌더링 원인은 크게 세 가지다:
- Props changed: 부모로부터 전달받은 props가 변경됨
- State changed: 컴포넌트 자체의 state가 변경됨
- Parent rendered: 부모 컴포넌트가 리렌더링됨 (props 변경 없이)
4. 불필요한 리렌더링 감지
4.1 리렌더링 하이라이트
React DevTools 설정에서 "Highlight updates when components render" 옵션을 활성화하면, 컴포넌트가 리렌더링될 때 해당 영역에 컬러 테두리가 표시된다.
- 파란색 테두리: 가벼운 렌더링
- 노란색/주황색 테두리: 비용이 높은 렌더링
- 빨간색 테두리: 매우 빈번하거나 비용이 높은 렌더링
이 기능을 켜놓고 페이지를 사용하면, 변경되지 않아야 할 영역에서 테두리가 깜빡이는 것을 육안으로 확인할 수 있다.
4.2 흔한 불필요한 리렌더링 패턴
패턴 1: 부모 리렌더링에 의한 자식 리렌더링
jsx
function Parent() {
const [count, setCount] = useState(0);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>+</button>
<ExpensiveChild /> {/* count와 무관하지만 매번 리렌더링됨 */}
</div>
);
}패턴 2: 인라인 객체/함수가 매 렌더링마다 새 참조를 생성 - style={{ color: "red" }}나 onClick={() => fn()} 같은 인라인 표현은 매 렌더링마다 새 참조를 만들어 자식의 memo를 무효화한다.
패턴 3: Context 변경이 불필요한 소비자까지 리렌더링 - Context의 일부 값만 변경되어도 해당 Context를 구독하는 모든 Consumer가 리렌더링된다.
5. React.memo, useMemo, useCallback 활용
5.1 React.memo
컴포넌트를 감싸서 props가 변경되지 않으면 리렌더링을 건너뛴다.
jsx
// 적용 전: 부모가 리렌더링될 때마다 함께 리렌더링됨
function PostItem({ title, author }) {
return <div>{title} - {author}</div>;
}
// 적용 후: title과 author가 동일하면 리렌더링 건너뜀
const PostItem = React.memo(function PostItem({ title, author }) {
return <div>{title} - {author}</div>;
});적용 기준:
- props가 자주 같은 값으로 전달되는 컴포넌트
- 렌더링 비용이 높은 컴포넌트 (복잡한 계산, 큰 리스트)
- 순수 표현(presentational) 컴포넌트
5.2 useMemo
계산 비용이 높은 값을 메모이제이션하여, 의존성이 변경되지 않으면 이전 결과를 재사용한다.
jsx
function PostList({ posts, searchTerm }) {
// 적용 전: 매 렌더링마다 필터링 수행
// const filteredPosts = posts.filter(p => p.title.includes(searchTerm));
// 적용 후: posts나 searchTerm이 변경될 때만 필터링 수행
const filteredPosts = useMemo(
() => posts.filter(p => p.title.includes(searchTerm)),
[posts, searchTerm]
);
return filteredPosts.map(post => <PostItem key={post.id} {...post} />);
}5.3 useCallback
함수를 메모이제이션하여, 의존성이 변경되지 않으면 동일한 함수 참조를 유지한다. React.memo로 감싼 자식 컴포넌트에 콜백을 전달할 때 특히 유용하다.
jsx
function Parent() {
const [count, setCount] = useState(0);
// 적용 전: 매 렌더링마다 새 함수가 생성되어 Child가 리렌더링됨
// const handleClick = () => console.log("clicked");
// 적용 후: 동일한 함수 참조가 유지되어 Child의 리렌더링 방지
const handleClick = useCallback(() => {
console.log("clicked");
}, []);
return (
<div>
<button onClick={() => setCount(c => c + 1)}>Count: {count}</button>
<MemoizedChild onClick={handleClick} />
</div>
);
}
const MemoizedChild = React.memo(function Child({ onClick }) {
console.log("Child rendered");
return <button onClick={onClick}>Click me</button>;
});5.4 최적화 도구 선택 가이드
| 상황 | 도구 | 이유 |
|---|---|---|
| 자식 컴포넌트가 불필요하게 리렌더링됨 | React.memo | props 동일 시 렌더링 건너뜀 |
| 비용이 높은 계산이 매 렌더링마다 수행됨 | useMemo | 의존성 기반 결과 캐싱 |
| memo된 자식에 전달하는 콜백이 매번 새로 생성됨 | useCallback | 함수 참조 안정화 |
| Context 변경이 불필요한 소비자까지 영향 | Context 분리 또는 useMemo | 구독 범위 최소화 |
6. 마이크로프론트엔드에서의 활용
6.1 마이크로앱별 프로파일링
각 마이크로앱은 독립적인 React 루트를 가질 수 있다. React DevTools의 Components 탭에서 복수의 React 렌더 트리가 표시된다면, 각각이 별도의 마이크로앱일 수 있다.
6.2 shared 컴포넌트 리렌더링 추적
Module Federation의 shared 라이브러리를 통해 공유되는 컴포넌트가 여러 마이크로앱에서 리렌더링되는 패턴을 추적한다. 특히 전역 상태(Context, Redux Store)가 변경될 때 모든 마이크로앱의 컴포넌트가 리렌더링되지 않는지 확인해야 한다.
6.3 프래그먼트 렌더링 비용 측정
프래그먼트로 노출된 컴포넌트가 호스트 앱에서 렌더링될 때의 비용을 Profiler로 측정한다. 프래그먼트의 렌더링이 호스트 앱의 다른 컴포넌트 렌더링을 차단하지 않는지 확인한다.
핵심 정리
- Components 탭으로 구조를 파악한다: React 컴포넌트 트리, Props, State, Hooks를 검사하고 실시간으로 수정하여 디버깅할 수 있다
- Profiler 탭으로 렌더링 비용을 측정한다: Flame Chart, Ranked Chart, Timeline 세 가지 뷰로 렌더링 성능을 다각도로 분석한다
- "Why did this render?"로 원인을 진단한다: Props changed, State changed, Parent rendered 중 어떤 이유로 리렌더링되었는지 정확히 파악할 수 있다
- 3가지 최적화 도구를 상황에 맞게 사용한다: React.memo(컴포넌트 메모이제이션), useMemo(계산 결과 캐싱), useCallback(함수 참조 안정화)을 적재적소에 활용한다
- 과도한 최적화를 경계한다: 측정 없이 memo, useMemo, useCallback을 남발하면 오히려 메모리 사용량이 늘고 코드 복잡도가 증가한다. 반드시 Profiler로 측정 후 필요한 곳에만 적용한다
다음 단계
- 다음: 성능 개선 실전 팁 - Lazy Loading, 번들 크기 분석, 이미지 최적화, 마이크로프론트엔드 특화 최적화 등 실전에서 바로 적용할 수 있는 성능 개선 기법을 학습한다