사이드 프로젝트 개발 과정 - (댓글 구현, 전처리 하기)

knh6269·2024년 6월 19일
1

ootd.zip

목록 보기
16/16
post-thumbnail

도입

ootdzip의 ootd게시물에는 유저와 소통할 수 있는 댓글 기능이 있다. 내가 디자인을 보고 구상한 댓글 구조를 백엔드에게 요청했더니, 인피니티 스크롤 이슈로 인해 그렇게 주기는 힘들다고 했다. 그래서 나는 그냥 ootd의 댓글 리스트를 받고 내가 전처리를 통해 원하는 구조대로 만들기로 했다. 오늘은 해당 과정에 대해 작성해보겠다.


요구사항

디자인

기능

  • 댓글은 본댓글/대댓글로 이루어져있다.
  • 대댓글 작성시 답글달기를 누른 댓글의 작성자가 자동으로 @username으로 표시된다.
  • 대댓글이 있는 본댓글이 삭제되면 본댓글은 삭제된 댓글입니다라는 내용으로 수정된다.
  • 대댓글이 없는 본댓글을 삭제하면 바로 사라진다.
  • 삭제된 댓글입니다라는 본댓글의 대댓글을 모두 삭제하면 본댓글은 사라진다.

댓글 리스트 전처리

서버에서 주는 댓글 리스트 형태

[
"id": 29,
"userId": 31,
"userName": "연휘", 
"userImage": ''
"content": "패완얼 어디갔지",
"timeStamp": "3주 전",
"taggedUserName": "",
"depth": 1,
"parentId": null,
"groupId": 1
],...

내가 원하는 댓글 리스트 형태

groupIddepth를 활용해 본댓글의 객체에 대댓글의 객체 배열이 포함된 형태
자식 댓글이 객체도 들어가야 하는 이유: 본댓글 대댓글의 컴포넌트가 다르기 때문

[
"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를 기준으로 댓글을 분리하기로 했다. depth1이라면 본댓글로 취급하고, 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);
});

자식 댓글 push

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를 추가한다.
또한 commentWritingtrue로 세팅한다. 그리고 댓글 작성 Inputfocus 해준다.

  const onClickReplyButton = (userName: string, commentId: number) => {
    setComment((previous) => {
      return {
        ...previous,
        taggedUserName: userName,
        parentDepth: 1,
        commentParentId: commentId,
      };
    });
    setCommentWriting(true);
    commentRef.current.focus();
  };

답글 작성 중 컴포넌트 렌더링

commentWritingtrue로 바뀌면 답글 남기는중 컴포넌트를 렌더링한다. x버튼을 클릭하면 작성중인 댓글 내용을 삭제하고 comment를 초기화하고 commentWritingfalse로 세팅한다.

<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에 변화가 생길 시 다시 댓글을 가져오게 했다.


고민점

새로운 댓글 작성 시 focus를 어디에?

ootdzip의 댓글은 오래된 순으로 불러온다. 현재는 댓글을 한번에 불러오지만, 설계는 현재 인피니티 스크롤로 되어있다. 내가 작성한 댓글은 가장 최근에 작성 되었기때문에, 아래로 가게 되는데, 아래에 있는 내가 작성한 댓글을 보여주려면 모든 댓글을 가져와야한다. 이러한 이슈를 다른 서비스에서는 어떻게 해결했는지 알아보았다.

인스타그램

인스타그램의 댓글 리스트 정렬 기준은 우리와 마찬가지로 오래된 순으로 정렬된다. 내가 작성한 댓글의 경우에는 우선 정렬 순위에 고려되 맨 위로 온다. 내가 작성한 대댓글의 경우에는 우선 정렬순위에 고려되지 않는다. 또한 내가 작성한 댓글은 맨 위로 오기 때문에 focus 되지 않는다.

slack

슬랙의 댓글 리스트 정렬 기준은 우리와 마찬가지로 오래된 순으로 정렬된다. 내가 작성한 댓글 또한 마찬가지로 오래된 순으로 정렬되어 리스트 맨 아래로 간다. 댓글을 남기는 Input이 맨 아래에 있어 댓글 작성 시 따로 focus 되지 않는다.

이렇듯 다른 서비스는 자기들만의 방법으로 내 고민을 해결한걸 확인했다. 이에 우리도 회의를 했는데, 인스타그램의 방식을 따르며, 댓글 모달을 따로 분리하기 위해 현재 모달창으로의 분리 계획에 있다.


정리

댓글의 도입으로 정말 커뮤니티로써의 모습이 많이 보이게 된 것 같다. 하지만 동시에 고려해야할 부분이 정말 많았다. 댓글 신고, 유저 차단 등 예외사항이 많았는데 다음 내용들은 다른 포스팅에서 다뤄보겠다. 완성된 댓글은 https://apps.apple.com/kr/app/ootdzip/id6499494035 에서 확인할 수 있다!

0개의 댓글