Skip to content

578줄짜리 친구 페이지를 탭 오케스트레이터로 쪼갠 이야기

친구 페이지 리디자인을 하면서 FriendListPage.tsx 한 파일에 몰려 있던 로직을 탭 단위로 나눴다. 578줄이 47줄로 줄고, 나머지는 탭·카드·모달 컴포넌트로 흩어졌다. 그때 썼던 패턴만 정리해 둔다.


리팩터링 전후

원래 한 파일에 친구 목록, 검색, 요청 송수신, 페르소나 상세 모달, 삭제 확인 다이얼로그가 다 들어가 있었다. 상태도 많고, 검색만 고치려 해도 목록 코드 근처를 건드리게 되고, 유저 카드 UI는 목록/검색/요청마다 비슷하게 중복돼 있었다.

지금은 페이지는 탭 상태 + 라우팅만 담당하고, 각 탭이 자기 데이터 fetching·상태·UI를 가진다.

  • FriendListPage.tsx — 47줄, 탭 오케스트레이터
  • FriendTabs.tsx — 세그먼트 컨트롤 + 배지
  • FriendListTab, FriendRequestsTab, FriendDiscoverTab — 탭별 비즈니스 로직
  • FriendUserCard — 공통 유저 카드
  • PersonaDetailModal — 페르소나 상세

총 줄 수는 파일이 나뉘면서 오히려 늘었지만, 파일당 평균한 파일 최대 줄 수는 크게 줄어서 읽기·수정이 훨씬 수월해졌다.


탭 오케스트레이터 + URL 파라미터

페이지는 탭 상태와 공통 데이터(배지용 요청 개수)만 관리하고, 실제 동작은 각 탭에 맡긴다.

탭 상태는 useState 대신 useSearchParams로 URL에 넣었다. ?tab=requests 같은 식이라 딥링크(알림에서 요청 탭으로 바로 이동), 뒤로가기, 새로고침 후 복원이 자연스럽다. 기본 탭(friends)은 파라미터 없이 /friends로 두고, 나머지만 쿼리로 넣었다.

URL 파라미터는 외부 입력이니까 화이트리스트로 검증한다. TAB_PARAM_MAP에 없는 값이 오면 기본 탭으로 폴백하는 식.


FriendUserCard — 통일 카드 + actions 주입

목록/요청/찾기에서 쓰는 유저 카드를 하나로 묶고, actions prop으로 탭마다 다른 버튼을 넣었다. 대화, 수락/거절, 요청 보내기 등 맥락에 따라 다른 액션만 넘기면 된다.

personaNameReact.ReactNode로 둬서, "대기 중" 같은 걸 <span className="text-amber-500">대기 중</span>처럼 스타일된 JSX로 넘길 수 있게 했다.


찾기 탭에서 관계 상태 O(1)로 쓰기

검색·추천 결과에서 "이 사람이 친구인지, 요청 보냈는지, 받았는지"에 따라 버튼이 달라져야 한다. 친구 목록·보낸/받은 요청을 TanStack Query로 이미 갖고 있으니까, 그걸로 Set/Map을 만들어 두고 getUserRelationStatus(userId)로 O(1) 조회한다. 받은 요청은 수락 시 request.id가 필요해서 userId → requestId Map을 썼다.


빈 상태에서 CTA

데이터가 없을 때 "데이터 없음"만 쓰지 않고, "친구 찾기" 버튼으로 discover 탭으로 넘어가게 했다. onNavigateDiscover는 페이지에서 handleTabChange('discover')를 넘겨서, 빈 상태에서도 다음 행동이 바로 보이게 했다.


리팩터링 순서

실제로 한 순서는 이랬다.

  1. FriendUserCard 먼저 뽑기 — 가장 단순하고 영향 범위 작음
  2. FriendTabs — 순수 UI
  3. 탭 컴포넌트 세 개 분리 — 비즈니스 로직 이동
  4. PersonaDetailModal 분리 — 상태 연결이 복잡한 부분은 나중에
  5. 페이지를 오케스트레이터로 축소 — 조각 다 준비된 뒤 조합
  6. 백엔드 API — 검색 결과에 페르소나 정보 포함하는 전용 엔드포인트 추가 (N+1 없이 IN 절로 한 번에 조회)

의존성이 적은 것부터 빼고, 페이지 구조는 조각이 갖춰진 다음에 바꾸는 게 중간에 깨진 상태를 짧게 가져간다.


언제 쪼개면 좋을지

파일이 300줄 넘고, 기능 변경이 들어오고, 같은 UI 패턴이 여러 곳에 반복되고, 새 기능 넣을 때마다 기존 코드에 손이 많이 가면 리팩터링을 고려해 볼 만하다. 이번은 "탭 기반 리디자인"이라는 기능 변경과 같이 진행해서, 구조 개선만 따로 하지 않고 흐름에 실었다.

탭 오케스트레이터, URL 기반 탭, 통일 카드 + actions, 관계 상태 Set/Map, 빈 상태 CTA 같은 패턴은 설정 페이지나 다른 다중 탭 화면에도 그대로 쓸 수 있다.

삽질 테크 블로그