문제
댓글이 이벤트, 게시글, 매치/응원, 비디오 네 영역에 시차를 두고 추가되면서, 담당자마다 요청·응답 타입이 제각각이었습니다. API 엔드포인트와 응답 구조는 물론 삭제·수정·신고·고정 권한까지 영역마다 달랐습니다. 대댓글이나 멘션 하나를 손보려면 네 곳을 서로 다른 방식으로 고쳐야 했습니다.
그 위에 대댓글, @멘션, 신고, 고정/해제, 하이퍼링크 파싱까지 기능이 계속 붙어야 하는 상황이었습니다.
먼저 응답 형태를 맞추기
프론트에서 변환 코드로 덮으면 복잡함이 프론트로 옮겨올 뿐이라 보고, 응답 형태 통일을 백엔드에 제안해 합의했습니다. 근거는 양쪽의 이득이었습니다. 프론트는 도메인별 래퍼 없이 공통 컴포넌트로 묶을 수 있고, 백엔드도 새 도메인에 같은 형태를 재사용할 수 있습니다.
조합하고, 주입받기
그 위에 댓글 UI를 역할별로 나누고, 데이터 조회·변경 훅만 밖에서 주입받도록 설계했습니다. 화면마다 필요한 조각이 달라 하나로 뭉친 컴포넌트에 플래그를 다는 대신 조합하는 쪽을 택했습니다.
Comment
├── Header 작성자 · 고정 표시 · 더보기(수정/삭제/신고)
├── Content 본문 · 멘션 렌더링 · 수정 모드
├── Footer 대댓글 토글/목록 ← 주입 지점
└── Reply 대댓글 입력 · 멘션 연동
도메인 결합은 Footer 한 곳에 모입니다. 조회 훅과 작성 훅을 props로 받기 때문에, 컴포넌트는 자기가 어느 API를 쓰는지 모릅니다.
// 이벤트 댓글
<Comment.Footer
useRepliesQuery={useEventRepliesList}
useCreateReplyMutation={useCreateEventComment}
/>
// 영상 댓글 - 훅만 교체
<Comment.Footer
useRepliesQuery={useVideoRepliesList}
useCreateReplyMutation={useCreateVideoComment}
/>
데이터는 층을 나눠 흘러갑니다. 도메인별 API 호출 위에 TanStack Query 훅을 두고, 쿼리 정의는 queryOptions 패턴으로 모아 캐시 키 체계를 통일했습니다. 페이지 컴포넌트는 그 훅을 골라 공통 댓글 컴포넌트에 주입하기만 합니다.
도메인별 API 호출 (useEventCommentApi, usePostCommentApi, ...)
↓
TanStack Query 훅 (useCreateComment, useRepliesList): queryOptions + 캐시 키 통일
↓
도메인별 페이지 컴포넌트
↓ 의존성 주입
공통 Comment 컴포넌트
변하는 것과 지킬 것
도메인마다 다른 건 콘텐츠 ID 타입뿐이라 그것만 제네릭으로 열고 나머지 시그니처는 고정했습니다. 변하는 것과 지켜야 할 것을 계약으로 분리한 것이 이 설계의 핵심입니다.
새 도메인은 훅 한 세트(약 50줄)만 추가하면 재사용되고, 대댓글·멘션 같은 공통 기능은 한 곳에서만 유지됩니다. 캐시는 도메인별로 키를 나눠 섞이지 않게 하되, 댓글을 새로 쓰면 댓글 목록과 대댓글 목록을 함께 무효화합니다.
권한도 같은 방식으로 다뤘습니다. 콘텐츠 소유자(Owner)와 댓글 작성자(Author) 플래그를 나눠, 어떤 메뉴를 보여 줄지를 이 두 플래그로 나눠 판단합니다.
멘션: 상태를 훅 안에 가두기
기능 중 가장 까다로웠던 건 @멘션이었습니다. 작성·수정 중에 멘션 위치를 추적하고, 글이 바뀔 때마다 인덱스를 다시 계산하고, 드롭다운을 열고 닫는 상태가 한 컴포넌트에 쌓이면서 컴포넌트가 비대해지고 렌더링에도 영향을 줬습니다.
핵심 로직을 useMention 훅으로 옮기고, 드롭다운 UI 상태는 MentionContext로 따로 떼어 입력과 무관한 부분이 다시 그려지지 않게 했습니다. 수정 모드는 기존 멘션을 복원하는 일만 다르므로, useMention을 안에서 재사용하는 useCommentEditMention을 따로 뒀습니다.
useMention (313줄): 핵심 로직
├── @ 입력 감지 → 유저 검색 API 호출
├── 멘션 드롭다운 UI 제어 (MentionContext)
├── 멘션 삽입 시 텍스트와 메타데이터를 함께 관리
└── 중복 멘션 방지
useCommentEditMention (107줄): 수정 전용
├── 기존 멘션 상태 복원
├── useMention 재사용
└── startEditing / cancelEditing 생명주기
초기 로딩에 짐이 되지 않게
무거운 댓글 에디터(CommentTextField)는 동적 import로 떼어내고, 댓글 데이터도 필요할 때 불러오도록(Lazy Fetch) 해서 댓글 때문에 첫 화면이 늦어지지 않게 했습니다.