개요
src/components/base-ui/Bookcase/bookid/ReviewList.tsx는 한줄평 목록 렌더링 컴포넌트이지만, 편집 상태 관리, 삭제 확인 모달 상태, 키보드 이벤트 처리, 그리고 모바일/태블릿 두 개의 독립된 레이아웃을 하나의 컴포넌트에서 모두 처리하고 있어 SRP를 위반합니다.
관련 PR: #273
현재 문제점
| 책임 |
관련 코드 |
| ① 편집 상태 관리 |
editingId, draftText, draftRating, startEdit, cancelEdit, editingItem |
| ② 삭제 확인 모달 상태 |
deleteTargetId, openDelete, closeDelete, confirmDelete |
| ③ 키보드 이벤트 처리 |
useEffect([editingId])에서 Escape 키 감지 시 편집 취소 |
| ④ 모바일 레이아웃 |
t:hidden 블록 (그리드 레이아웃, 인라인 편집 폼) |
| ⑤ 태블릿+ 레이아웃 |
hidden t:flex 블록 (플렉스 레이아웃, 별점 위치 다름) |
제안하는 분리 방향
1. useReviewItemEdit (편집 상태 + 키보드 이벤트 전담)
// src/hooks/useReviewItemEdit.ts
// 역할: 현재 편집 중인 한줄평 ID, 임시 텍스트/별점 상태 관리 및 Escape 키 취소만 담당
export function useReviewItemEdit(): {
editingId: ReviewItem['id'] | null;
draftText: string;
draftRating: number;
setDraftRating: (v: number) => void;
startEdit: (item: ReviewItem) => void;
cancelEdit: () => void;
isEditing: (id: ReviewItem['id']) => boolean;
}
2. useConfirmDelete (삭제 확인 모달 상태 전담 - 범용)
// src/hooks/useConfirmDelete.ts
// 역할: 삭제 대상 ID 저장, 모달 열기/닫기, 삭제 실행 콜백 호출만 담당
export function useConfirmDelete<T>(
onDelete: (id: T) => void
): {
deleteTargetId: T | null;
openDelete: (id: T) => void;
closeDelete: () => void;
confirmDelete: () => void;
}
3. ReviewListItem (단일 한줄평 아이템 컴포넌트 분리)
// src/components/base-ui/Bookcase/bookid/ReviewListItem.tsx
// 역할: 단일 한줄평의 모바일/태블릿 레이아웃 렌더링만 담당
// 편집 상태와 삭제 상태는 props로 주입받음
type Props = {
item: ReviewItem;
isEditing: boolean;
draftText: string;
draftRating: number;
canManage: boolean;
onStartEdit: () => void;
onCancelEdit: () => void;
onConfirmEdit: (text: string, rating: number) => void;
onOpenDelete: () => void;
onReport: () => void;
onClickAuthor: (name: string) => void;
onChangeDraftRating: (v: number) => void;
};
export default function ReviewListItem(props: Props) { ... }
4. 개선 후 ReviewList
// ReviewList는 상태 조합 + 목록 반복만 담당
export default function ReviewList({ items, isStaff, onReport, onUpdate, onDelete, onClickAuthor }: Props) {
const editState = useReviewItemEdit();
const deleteState = useConfirmDelete(onDelete);
return (
<>
<BookshelfDeleteConfirmModal
isOpen={deleteState.deleteTargetId != null}
onConfirm={deleteState.confirmDelete}
onClose={deleteState.closeDelete}
...
/>
{items.map((item) => (
<ReviewListItem
key={item.id}
item={item}
isEditing={editState.isEditing(item.id)}
...
/>
))}
</>
);
}
기대 효과
useConfirmDelete는 발제 목록(DebateList) 등 다른 삭제 확인이 필요한 곳에 재사용 가능
ReviewListItem이 순수 렌더링 컴포넌트가 되어 스냅샷 테스트 작성 용이
- ReviewList 자체는 50줄 이하로 축소 가능
수정 대상 파일
src/components/base-ui/Bookcase/bookid/ReviewList.tsx
- (신규)
src/components/base-ui/Bookcase/bookid/ReviewListItem.tsx
- (신규)
src/hooks/useReviewItemEdit.ts
- (신규)
src/hooks/useConfirmDelete.ts
개요
src/components/base-ui/Bookcase/bookid/ReviewList.tsx는 한줄평 목록 렌더링 컴포넌트이지만, 편집 상태 관리, 삭제 확인 모달 상태, 키보드 이벤트 처리, 그리고 모바일/태블릿 두 개의 독립된 레이아웃을 하나의 컴포넌트에서 모두 처리하고 있어 SRP를 위반합니다.관련 PR: #273
현재 문제점
editingId,draftText,draftRating,startEdit,cancelEdit,editingItemdeleteTargetId,openDelete,closeDelete,confirmDeleteuseEffect([editingId])에서Escape키 감지 시 편집 취소t:hidden블록 (그리드 레이아웃, 인라인 편집 폼)hidden t:flex블록 (플렉스 레이아웃, 별점 위치 다름)제안하는 분리 방향
1.
useReviewItemEdit(편집 상태 + 키보드 이벤트 전담)2.
useConfirmDelete(삭제 확인 모달 상태 전담 - 범용)3.
ReviewListItem(단일 한줄평 아이템 컴포넌트 분리)4. 개선 후 ReviewList
기대 효과
useConfirmDelete는 발제 목록(DebateList) 등 다른 삭제 확인이 필요한 곳에 재사용 가능ReviewListItem이 순수 렌더링 컴포넌트가 되어 스냅샷 테스트 작성 용이수정 대상 파일
src/components/base-ui/Bookcase/bookid/ReviewList.tsxsrc/components/base-ui/Bookcase/bookid/ReviewListItem.tsxsrc/hooks/useReviewItemEdit.tssrc/hooks/useConfirmDelete.ts