마이 블로그 프로젝트 06 - 댓글 기능.

이유승·2023년 7월 18일
0

보통 블로그를 보면, 게시글 아래 댓글을 작성하고 조회하는 기능을 포함하고 있다. 나도 어쨌든 '블로그'를 만드는 입장이니 만큼 댓글 기능에 대해서도 무관심할 수는 없다.

사실 댓글 기능은 게시글 기능과 거의 다를 것이 없다. 작성한 내용을 저장하고, 조회해서 가져오는 기본 형태가 완전히 동일하기 때문이다.

다만 댓글의 DB는 어떻게 구성해야하며 해당 글에 작성된 댓글을 구분하는 방법은 무엇인지 생각해야하는게 관건일 뿐이다.



1. 이 댓글은 누구의 댓글이요.

댓글은 게시글 내용 조회 페이지에서 작성한다. 그렇다면 게시글의 문서 id값을 그대로 사용할 수 있다는 것이고, 댓글을 작성했을 때 DB에 이 댓글이 어떤 글에 달려있는 것인지 구분하는 기준이 되도록 구조를 만들어주었다. targetDoc.



2. 리렌더링은 어떻게 하지?

댓글을 작성하고 나면 화면이 바로 리렌더링되어 내가 작성한 댓글이 화면에 출력되어야 한다. 리렌더링 자체는 리액트가 알아서 해주는 것이고 댓글 작성 이후에 댓글 데이터를 조회하는 함수를 다시 호출해주기만 하면 되는데..

얼라리요.. 친숙한 에러가 날 반겨준다.

const { getComments, addComments, responseData } = useFirestoreComt('comment');

문제의 원인은 댓글을 작성하는 addComments 함수와 댓글 데이터를 가져오는 getComments 함수가 동일한 Hooks를 공유하고 있다는 것. 원래대로면 각각의 동작이 따로 실행되기 때문에 문제가 없어야 하는데, 내가 기능을 구현할 때 댓글을 작성했을 때 정상동작 여부를 확인하기 위해서 Context API를 통해 댓글 작성시 생성되는 파이어베이스 객체를 저장하도록 구현한 것이 문제가 되었다.

    case 'addComment':
        return { isPending: false, document: action.payload, success: true, error: null }
        

파이어베이스와 Context API를 배우면서 진행했던 프로젝트였던 지라 공부의 목적으로 작성했던 코드였던지라.. 어쨌든 배열 객체 형식의 댓글 데이터를 담아야하는 responseData에 파이어베이스 객체를 들어가서 에러가 발생한 것이다. (배열 데이터가 들어와야 하는 곳에 객체 데이터가 들어오니 에러가 발생.)

해결 방법은 그냥 파이어베이스 객체가 저장되지 않도록 해주면 되지만, 전역 변수 관리를 하면서 데이터 형식이 바뀌는 것은 흔하게 발생하는 일인지라 프론트단에서 이 문제를 해결해보기로 하였다.



3. 문제를 해결하자.

const [isReRender, setIsReRender] = useState(false);

const onSubmitEvent = (event) => {
    event.preventDefault();
    addComments(commentsData, id, pageType);
    setCommentsData([]);
    setIsReRender(true);
};

우선 댓글을 작성했을 때 useEffect와 디펜던시를 이용하여 리렌더링을 발생시키기 위해 isReRender 플래그 변수를 새롭게 추가해주었다.

댓글을 작성할 경우, 작성 함수를 호출하고 댓글 작성 UI에 남아있는 내용을 초기화해준 다음에 isReRender 변수를 조정한다.

useEffect(() => {
    if (isReRender) {
        getComments(id);

        if (Array.isArray(responseData.document)) {
            setCommentsList(responseData.document);
        };

        setIsReRender(false);
    };
    // eslint-disable-next-line
}, [isReRender]);

useEffect는 isReRender 변수가 변경되었을 때마다 동작하는데, 이대로는 무한 반복 에러가 발생하므로 조건문을 사용하여 isReRender이 true일 때만 코드가 동작하도록 해주었다.

댓글 데이터를 새롭게 조회하고, 반환되오는 데이터가 배열의 형태인지 확인한 다음 배열이 맞으면 setState. 테스트 해보니.. 댓글을 작성하고 화면이 잘 리렌더링 되었다.

코드 평가.

평가 방법, 개인적인 코드 리뷰 및 Chat GPT 사용.

-> 데이터 유효성을 확인하지 않음. 입력한 값이 제대로 존재하는지 확인하는 절차가 필요함.

-> 컴포넌트 분할 필요. 다양한 역할을 하는 코드들이 하나의 파일 내부에 너무 많이 선언되어 있음. 유지보수 측면과 코드 가독성 면에서 컴포넌트를 분리할 필요성이 있음.

-> 무한 리렌더링 문제. useEffect의 디펜던시에 isReRender가 사용되고 있는데, 내부에서 isReRender을 변경하고 있으므로 무한 리렌더링이 발생하게 됨. 조건문 때문에 코드가 무한하게 동작하지는 않지만 렌더링이 반복될 수밖에 없음.
-> 댓글 작성 이후 에러 해결에 급급하여 문제가 해결되었으니 다 잘되있을 것이라 생각하였다. 그런데 나중에 코드를 리뷰해보니 무한 리렌더링을 발생시키는 어이없는 코드를 작성해둔 것을 확인하였다. 그런데 기능을 테스트해보니 무한 리렌더링이 발생하지 않는다!
-> 이 기적의 이유는 useFirestoreComt 커스텀 Hook을 통해서 responseData를 받아오는 것에서 비롯되는 것은 확실한데 명확한 이유를 알 수가 없다..
-> Chat GPT를 이용해본 결과, 다음과 같이 설명해주고 있는데 아무래도 더 머리를 굴려봐야할 것 같다. (물론 다음부터는 이런 구조로 기능이 돌아가도록 하면 안된다! 절대로!)

useEffect 내에서 getComments, setCommentsList, 그리고 setIsReRender를 호출하여 리렌더링이 발생하지 않는 이유는 다음과 같습니다:

isReRender 상태 변경 시점:
isReRender 상태는 onSubmitEvent 함수 내에서 setIsReRender(true)로 변경됩니다. onSubmitEvent 함수는 댓글을 작성하는 폼을 제출했을 때 호출되며, 이때만 isReRender 상태가 true로 변경됩니다.

useEffect 의존성 배열:
useEffect 훅의 의존성 배열은 [isReRender]로 지정되어 있습니다. 이 의존성 배열은 useEffect를 호출하는 조건으로 사용됩니다. 즉, isReRender 상태가 변경될 때만 useEffect가 실행됩니다.

useFirestoreComt 커스텀 훅과 responseData 상태:
useFirestoreComt 커스텀 훅에서 상태 업데이트를 담당하는 dispatch 함수를 사용하여 responseData 상태를 변경합니다. 이 때, responseData는 useReducer를 사용하여 상태를 관리하는데, dispatch를 호출하여 상태를 변경하면 해당 컴포넌트가 리렌더링됩니다.

responseData 상태 변경:
useFirestoreComt 커스텀 훅 내의 getComments 함수에서는 API 호출을 통해 데이터를 가져오고, dispatch를 호출하여 responseData 상태를 업데이트합니다. 그러나 responseData 상태 변경은 isReRender 상태가 변경되는 시점이 아니라, API 호출 완료 시점에 발생합니다.

따라서 useEffect 내에서 getComments, setCommentsList, setIsReRender를 호출해도 isReRender 상태 변경 시점과 responseData 상태 변경 시점이 서로 다르기 때문에 무한 리렌더링이 발생하지 않습니다. useEffect는 isReRender 상태가 변경되는 경우에만 실행되고, API 호출에 의해 responseData 상태가 변경되더라도 useEffect 호출을 유발하지 않습니다.

profile
프론트엔드 개발자를 준비하고 있습니다.

1개의 댓글

comment-user-thumbnail
2023년 7월 18일

항상 좋은 글 감사합니다.

답글 달기