문제 상황
- 한 페이지 컴포넌트에
useState, useEffect가 난립하고 데이터 페칭/상태 파생/이벤트 처리 로직이 뒤섞여 UI 코드의 가독성과 유지보수성이 저하됨.
- 동일·유사 로직이 여러 곳에서 반복되어 버그 수정과 기능 확장이 비효율적임.
환경
- 프로젝트: React 기반 웹 애플리케이션
- 스택: React, TypeScript, (Next.js 사용 환경에서도 동일 원칙 적용 가능)
증상/코드 냄새
긴 useEffect 체인, 복잡한 의존성 배열, 데이터 페칭 중복, 파생 상태 계산이 UI 레이어와 결합
테스트 어려움(비즈니스 로직이 컴포넌트 수명주기와 강결합), 재사용성 낮음
원인 분석
- 관심사 분리(데이터/비즈니스 vs. 표현/UI)가 이뤄지지 않아 컴포넌트가 과도한 책임을 가짐.
- 비동기 페칭·에러·로딩·파생 상태 계산이 컴포넌트 곳곳에 산재하며 의존성 관리와 사이드이펙트 제어가 어려움.
해결 방법
- 추출 기준 정의
- 데이터 페칭, 파생 상태 계산, 폼/상태 전이 로직을 우선 추출 대상로 선정.
- 컴포넌트는 DOM·스타일·이벤트 바인딩 등 표현 로직에 집중하도록 역할 축소.
- Custom Hook 설계
- 입력: 쿼리 파라미터/초깃값/옵션(디바운스, 캐싱 키 등).
- 출력: 데이터, 로딩/에러, 파생 상태, 액션 핸들러(리프레시/뮤테이션 등).
- 계약(입출력 타입)을 명확히 하여 호출하는 컴포넌트가 쉽게 사용하도록 설계.
- 부작용 관리 강화
- 요청 취소(AbortController)로 경쟁 상태(race) 방지, 의존성 변경 시 정리(cleanup) 철저.
- 에러 분류(네트워크/검증/서버) 및 재시도/백오프 정책 옵션화.
- 성능/경험 개선
- 디바운스/스로틀, 결과 캐싱(키 기반 메모이제이션) 적용.
- 불필요한 재렌더를 줄이기 위해 반환 객체는
useMemo, 콜백은 useCallback으로 안정화.
- 타이핑/테스트
- 제네릭 타입으로 데이터 스키마를 표현, 오류 전파 타입 명시.
- 훅 단위 테스트로 페칭 성공/실패, 의존성 변경, 액션 호출 시나리오 검증.
- 컴포넌트 단순화
- 컴포넌트는 훅을 호출해 값과 핸들러만 받아 UI를 렌더.
검증
- 페이지 컴포넌트 LOC 감소, 로직 응집도 증가, UI/비즈니스 코드 분리가 명확해짐.
- 동일 훅을 다수 화면에서 재사용 가능해 수정 비용 감소.
- 의존성 변경 시 재페칭·캐싱 동작이 기대대로 수행되는지 테스트 통과.
배운 점
- 관심사 분리를 통해 컴포넌트는 UI에 집중하고, 비즈니스 로직은 훅으로 표준화할 수 있음.
- 명확한 계약(입출력 타입)과 부작용 제어가 리팩토링 이후의 안정성을 담보함.
- 재사용 가능한 훅은 향후 기능 확장과 성능 최적화의 기반이 됨.
참고
- React 공식 문서: Hooks, Custom Hooks 베스트 프랙티스