ootdzip
의 ootd게시물에는 유저와 소통할 수 있는 댓글 기능이 있다. 내가 디자인을 보고 구상한 댓글 구조를 백엔드에게 요청했더니, 인피니티 스크롤 이슈로 인해 그렇게 주기는 힘들다고 했다. 그래서 나는 그냥 ootd의 댓글 리스트를 받고 내가 전처리를 통해 원하는 구조대로 만들기로 했다. 오늘은 해당 과정에 대해 작성해보겠다.
본댓글/대댓글
로 이루어져있다.@username
으로 표시된다.삭제된 댓글입니다
라는 내용으로 수정된다.삭제된 댓글입니다
라는 본댓글의 대댓글을 모두 삭제하면 본댓글은 사라진다.[
"id": 29,
"userId": 31,
"userName": "연휘",
"userImage": ''
"content": "패완얼 어디갔지",
"timeStamp": "3주 전",
"taggedUserName": "",
"depth": 1,
"parentId": null,
"groupId": 1
],...
groupId
와 depth
를 활용해 본댓글의 객체에 대댓글의 객체 배열이 포함된 형태
자식 댓글이 객체도 들어가야 하는 이유: 본댓글 대댓글의 컴포넌트가 다르기 때문
[
"id": 29,
"userId": 31,
"userName": "연휘",
"userImage": ''
"content": "패완얼 어디갔지",
"timeStamp": "3주 전",
"taggedUserName": "",
"depth": 1,
"groupId": 1
"parentId": null,
"childComment": [
"id": 30,
"userId": 13,
"userName": "낙현",
"userImage": ''
"content": "ㅋㅋㅋ 새로운 글이야",
"timeStamp": "3주 전",
"taggedUserName": "연휘",
"parentId": 29,
"depth": 2,
"groupId": 1,
]
],...
Map
자료 구조를 활용해 본댓글 id
를 기준으로 댓글을 분리하기로 했다. depth
가 1이라면 본댓글로 취급하고, parentId
가 있다면 해당 id
를 키값으로 가지고 있는 value의 childComment
에 해당 댓글을 push
해주기로 했다.
Map 자료구조란?
Map은 리스트나 배열처럼 순차적으로 해당 요소 값을 구하지 않고 Key를 통해 Value를 얻는 자료구조이다.
값(Value)은 중복될 수 있지만, Key는 고유한 값(Unique)을 가져야 한다.
Map은 저장 순서를 유지할 필요가 없고, Key를 통해 Value를 얻어내기 때문에 Key는 중복을 허용하지 않는다.
const map = new Map<number, PostingCommentData>();
content.forEach((comment: PostingCommentData) => {
if (comment.depth === 1) map.set(comment.id, comment);
});
content.forEach((comment: PostingCommentData) => {
if (comment.parentId !== null) {
const parentComment = map.get(comment.parentId!);
if (parentComment) {
if (!parentComment.childComment) {
parentComment.childComment = [];
}
parentComment.childComment.push(comment);
}
} else {
resultData.push(comment);
}
});
setData(resultData);
댓글은 본댓글
에 다는지, 대댓글
에 다는지를 구분 해 주어야한다. 만약 대댓글
이라면 태그된 유저가 누구인지, 본 댓글의 id는 무엇인지 구분해 주어야한다. 이를 토대로 상태를 만들어줬다.
interface CommentStateType {
ootdId: number;
parentDepth: number;
content: string;
taggedUserName?: string;
commentParentId?: number;
}
const [comment, setComment] = useState<CommentStateType>({
ootdId: 0,
parentDepth: 0,
content: '',
});
댓글 Input
에 내용 작성 시 comment.conent
를 변경한다.
<S.Input
ref={commentRef}
onChange={(e) => {
setComment({ ...comment, content: e.target.value });
}}
placeholder="댓글을 남겨보세요."
value={comment.content}
/>
답글 달기
버튼 클릭 시 comment
상태에 taggedUser
, parentDepth
, commentParentId
를 추가한다.
또한 commentWriting
을 true
로 세팅한다. 그리고 댓글 작성 Input
을 focus 해준다.
const onClickReplyButton = (userName: string, commentId: number) => {
setComment((previous) => {
return {
...previous,
taggedUserName: userName,
parentDepth: 1,
commentParentId: commentId,
};
});
setCommentWriting(true);
commentRef.current.focus();
};
commentWriting
가 true
로 바뀌면 답글 남기는중 컴포넌트를 렌더링한다. x버튼을 클릭하면 작성중인 댓글 내용을 삭제하고 comment
를 초기화하고 commentWriting
을 false
로 세팅한다.
<AiFillCloseCircle
onClick={() => {
setComment({
...comment,
content: '',
});
setCommentWriting(false)};
className="closeButton"
}}
댓글 등록 버튼을 누르면 comment
를 담은 댓글 등록 api
를 서버로 보낸다.
const registerComment = async () => {
if (comment.content === '') return;
await postOOTDComment(comment);
setComment({
...comment,
content: '',
});
setCommentWriting(false);
//댓글 등록 api 연동
};
리팩토링 - 내가 등록한 댓글이 새로고침을 하기 전에 안보이는 상황
const [reRender, setRerender] = useState<number>(0);
상태를 선언해 댓글 등록 시setRerender(reRender + 1)
를 실행 해reRender
를 변경시켜준다.
그리고 댓글 리스트를 가져오는useEffect
의존성 배열에reRender
를 주입해reRender
에 변화가 생길 시 다시 댓글을 가져오게 했다.
ootdzip
의 댓글은 오래된 순으로 불러온다. 현재는 댓글을 한번에 불러오지만, 설계는 현재 인피니티 스크롤로 되어있다. 내가 작성한 댓글은 가장 최근에 작성 되었기때문에, 아래로 가게 되는데, 아래에 있는 내가 작성한 댓글을 보여주려면 모든 댓글을 가져와야한다. 이러한 이슈를 다른 서비스에서는 어떻게 해결했는지 알아보았다.
인스타그램의 댓글 리스트 정렬 기준은 우리와 마찬가지로 오래된 순으로 정렬된다. 내가 작성한 댓글의 경우에는 우선 정렬 순위에 고려되 맨 위로 온다. 내가 작성한 대댓글의 경우에는 우선 정렬순위에 고려되지 않는다. 또한 내가 작성한 댓글은 맨 위로 오기 때문에 focus 되지 않는다.
슬랙의 댓글 리스트 정렬 기준은 우리와 마찬가지로 오래된 순으로 정렬된다. 내가 작성한 댓글 또한 마찬가지로 오래된 순으로 정렬되어 리스트 맨 아래로 간다. 댓글을 남기는 Input
이 맨 아래에 있어 댓글 작성 시 따로 focus 되지 않는다.
이렇듯 다른 서비스는 자기들만의 방법으로 내 고민을 해결한걸 확인했다. 이에 우리도 회의를 했는데, 인스타그램의 방식을 따르며, 댓글 모달을 따로 분리하기 위해 현재 모달창으로의 분리 계획에 있다.
댓글의 도입으로 정말 커뮤니티로써의 모습이 많이 보이게 된 것 같다. 하지만 동시에 고려해야할 부분이 정말 많았다. 댓글 신고, 유저 차단 등 예외사항이 많았는데 다음 내용들은 다른 포스팅에서 다뤄보겠다. 완성된 댓글은 https://apps.apple.com/kr/app/ootdzip/id6499494035 에서 확인할 수 있다!