
React DevTools 활용
React 앱이 느린 이유를 찾지 못해 코드만 뒤지고 있었는데, React DevTools의 Profiler와 Components 탭이 범인을 바로 찾아줬다.

React 앱이 느린 이유를 찾지 못해 코드만 뒤지고 있었는데, React DevTools의 Profiler와 Components 탭이 범인을 바로 찾아줬다.
분명히 클래스를 적었는데 화면은 그대로다? 개발자 도구엔 클래스가 있는데 스타일이 없다? Tailwind 실종 사건 수사 일지.

안드로이드는 Xcode보다 낫다고요? Gradle 지옥에 빠져보면 그 말이 쏙 들어갈 겁니다. minSdkVersion 충돌, Multidex 에러, Namespace 변경(Gradle 8.0), JDK 버전 문제, 그리고 의존성 트리 분석까지 완벽하게 해결해 봅니다.

서버에서 잘 오던 데이터가 갑자기 앱을 죽입니다. 'type Null is not a subtype of type String' 에러의 원인과, 안전한 JSON 파싱을 위한 Null Safety 전략을 정리해봤습니다.

느리다고 느껴서 감으로 최적화했는데 오히려 더 느려졌다. 프로파일러로 병목을 정확히 찾는 법을 배운 이야기.

대시보드에 100개 정도의 항목을 보여주는 리스트를 만들었다. 로컬에서는 괜찮았는데 데이터가 늘어나니까 체감상 확실히 느려졌다. 스크롤할 때마다 버벅거리고, 검색 필터를 바꾸면 1-2초 정도 멈춘 것처럼 보였다.
문제는 "어디가 문제인지" 알 수가 없었다는 거다. 코드를 봐도 특별히 이상한 부분이 없었다. console.log를 여기저기 찍어봤지만 컴포넌트가 언제, 왜 다시 렌더링되는지 감이 안 왔다. "이 컴포넌트가 매번 렌더링되나?" 싶어서 로그를 찍으면 정말 매번 찍히는데, 그게 정상인지 문제인지 판단이 안 섰다.
그러다가 동료가 던진 한마디: "DevTools Profiler 켜봤어?"
아, 그게 있었지. Chrome DevTools만 쓰다가 React DevTools는 설치만 해두고 제대로 써본 적이 없었다. 열어보니 Components 탭이랑 Profiler 탭 두 개가 있었다. 이게 뭐가 다른 건지도 모르고 일단 Profiler부터 켜봤다.
Profiler 탭에서 파란색 녹화 버튼을 눌렀다. 그리고 내 앱에서 검색 필터를 한번 바꿔봤다. 그 순간, DevTools에 형형색색의 불꽃 그래프(flame graph)가 나타났다. 각 컴포넌트가 얼마나 렌더링 시간을 썼는지 막대 길이로 보여주는 거였다.
// 문제가 있던 코드
function Dashboard() {
const [searchTerm, setSearchTerm] = useState('');
const [items, setItems] = useState([]);
// 이 객체가 매번 새로 만들어진다
const filterConfig = {
caseSensitive: false,
includeArchived: true
};
return (
<div>
<SearchBar value={searchTerm} onChange={setSearchTerm} />
<ItemList items={items} filter={filterConfig} searchTerm={searchTerm} />
</div>
);
}
function ItemList({ items, filter, searchTerm }) {
console.log('ItemList rendered'); // 이게 계속 찍힌다
return (
<div>
{items.map(item => (
<ItemCard key={item.id} item={item} filter={filter} />
))}
</div>
);
}
Profiler의 flame graph를 보니까 ItemList가 노란색(중간 정도 느림)으로 떴고, 그 아래 100개의 ItemCard가 전부 회색(렌더링됨)으로 표시됐다. 검색어를 하나 바꿨을 뿐인데 100개 카드가 전부 다시 렌더링됐다는 뜻이다.
그런데 진짜 놀라운 건 Profiler의 "Why did this render?" 기능이었다. ItemList를 클릭하니까 이렇게 나왔다:
Why did this render?
- Props changed: filter
- Props changed: searchTerm
searchTerm이 바뀌는 건 당연한데, filter는 왜 바뀐 거지? 코드를 다시 보니 filterConfig 객체를 매번 새로 만들고 있었다. JavaScript에서는 {} !== {}이니까 React가 보기엔 props가 바뀐 거였다. 이게 내가 찾던 범인이었다.
Profiler로 범인을 찾았으니 이제 고칠 차례다. 근데 고치기 전에 Components 탭을 먼저 열어봤다. 이건 내 앱의 컴포넌트 트리를 보여주는 거였는데, 마치 HTML 구조를 보는 것처럼 컴포넌트 계층이 쫙 펼쳐져 있었다.
ItemList를 클릭하니까 오른쪽에 props, state, hooks가 전부 보였다. filter prop을 클릭해보니까 실제 객체 내용도 볼 수 있었다. 그리고 제일 유용했던 건 "rendered by" 정보였다. 이 컴포넌트가 어느 부모 컴포넌트 때문에 렌더링됐는지 추적할 수 있었다.
DevTools 설정에서 "Highlight updates when components render"를 켜니까 진짜 눈으로 보였다. 검색어를 바꿀 때마다 화면에 파란색 테두리가 깜빡이는데, 어느 부분이 리렌더링되는지 실시간으로 보이는 거였다. 100개 카드가 전부 깜빡이는 걸 보니까 확신이 들었다. "이거 고쳐야 해."
DevTools로 문제를 정확히 파악했으니 이제 고치는 건 간단했다. 세 단계로 접근했다:
1단계: 불필요한 객체 생성 제거function Dashboard() {
const [searchTerm, setSearchTerm] = useState('');
const [items, setItems] = useState([]);
// useMemo로 객체를 메모이제이션
const filterConfig = useMemo(() => ({
caseSensitive: false,
includeArchived: true
}), []); // 의존성 없으니 한번만 생성됨
return (
<div>
<SearchBar value={searchTerm} onChange={setSearchTerm} />
<ItemList items={items} filter={filterConfig} searchTerm={searchTerm} />
</div>
);
}
2단계: 불필요한 리렌더링 방지
// React.memo로 props가 실제로 바뀔 때만 리렌더링
const ItemCard = React.memo(({ item, filter }) => {
return (
<div className="card">
<h3>{item.title}</h3>
<p>{item.description}</p>
</div>
);
});
3단계: Profiler로 검증
다시 Profiler를 켜고 똑같은 동작을 해봤다. 이번엔 ItemCard 100개가 회색 대신 "Did not render"라고 나왔다. 검색어가 바뀌어도 필터 설정이 안 바뀌었으니 카드들은 리렌더링되지 않은 거다. Flame graph의 막대도 훨씬 짧아졌다.
체감 속도 차이가 확실했다. 검색 필터를 바꿨을 때 1-2초 걸리던 게 즉각 반응하게 됐다. 스크롤도 훨씬 부드러웠다.
이 경험으로 깨달은 건, 성능 최적화는 "감"이 아니라 "데이터"로 해야 한다는 거다.
코드만 보면 "이 부분이 느릴 것 같은데?"라는 추측만 할 뿐이다. React DevTools는 추측을 확신으로 바꿔줬다. Profiler는 "어느 컴포넌트가 느린지", Components 탭은 "왜 렌더링됐는지", Highlight updates는 "실제로 어떻게 동작하는지" 눈으로 보여줬다.
처음엔 모든 리스트에 React.memo를 다 붙이려고 했다. 전형적인 "과잉 최적화"였다. 근데 Profiler로 측정해보니까 실제로 문제가 되는 컴포넌트는 2-3개뿐이었다. 나머지는 리렌더링되어도 충분히 빨랐다. DevTools 없었으면 쓸데없는 메모이제이션 코드만 잔뜩 추가했을 거다.
React DevTools는 마치 의사가 쓰는 CT 스캐너 같았다. 겉으로 보면 "어디가 아픈 것 같긴 한데" 수준이지만, 스캔을 해보면 정확히 어디가 문제인지 보인다. 이제는 성능 문제가 생기면 코드를 뒤지기 전에 DevTools부터 켠다. 5분 프로파일링이 5시간 삽질을 줄여준다.