react + firebase 실시간 채팅앱 구현중 문제 직면

이명진·2023년 9월 14일
1

react 와 firebase 로 실시간 채팅앱을 구현하기로 하였다.

처음에는 firebase 9 버전으로 기능을 구현하고자 하였다. 비록 사용자가 적긴해서 자료도 firebase 8버전보다 적었지만 공식문서 같은데서 어찌저찌 해석을 하며 구현을 하였지만 문제를 만나게 되었다.

1. 구조적인문제 직면

공식문서 같은 경우에도 컬렉션 > 도큐먼트 > 필드 이런 방식으로 스토어 구조가 되어 있어서

필드 까지 접근하는 것은 잘 나와 있었다. 그러나 이번에 내가 구현 해야 할 구조는

컬렉션 > 도큐먼트 > 컬렉션> 도큐먼트 > 필드 이런 구조였다.

도큐먼트에서 다시 컬렉션으로 접근을 해야 하는데 이런방식은 자료가 많이 부족하였다.
공식문서를 확인해보며 각각 매개변수 옵션의 함수들을 찾아봤는데 시간도 촉박하고 다 찾아보기에는 무리가 있어서 firebase 8 로 낮춰서 구현하게 되었다.

다행히도 나같은 컬렉션 > 도큐먼트 > 컬렉션> 도큐먼트 > 필드 구조로 접근하시는 분도 계셔서
벨로그와 깃허브를 참고하여서 코드를 작성할수 있었다.

firebase 8에서는 닷노테이션으로 계속 연결해서 컬렉션, 도큐먼트 에 계속 접근이 가능했는데
왜 9에서는 안되게 했는지.. ㅠ

사람들의 코드를 참고하면서 코드를 완성할수 있었는데 또 다른 문제에 직면하게 되었다.

2. 무한 루프 문제

다른 분들의 코드를 참고해서 잘 코드를 작성할수 있었는데 구조가 useEffect로 감시를 하며 setState를 사용해서 데이터를 보관하는 형식이었다.

따로 훅으로 만들어서 사용했는데 useEffect와 setState 의 영향으로 계속 렌더링이 되는 것이 문제였다.

querySnapshot으로 database를 연결해놓고 있는데 연결해놓고 새로운 값이 들어올때마다 setState로 값을 저장하니까 다시 재렌더링이 되고 useEffect가 다시 호출되어서 무한으로 querySnapshot 이 연결되는 문제가 발생하게 되었다.
처음에는 계속 연결해야 하니 당연한 것이라고 생각하기도 했지만 렌더링이 계속되면서 성능적인 측면에서 문제가 없을까 생각하게 되었다.

넘기며 다른 것을 구현해야지 하고 계속 테스트를 하게 되었는데 결국 무료 계정은 5만회까지 읽기가 가능하다는 점을 알게 되었다. 테스트를 하려니까 오류가 계속뜨며 데이터를 불러오지 못하였다.

계속 재랜더링이 되고 호출하니 5만번 호출이 넘을수밖에…

이를 어떻게 해결해야 하나.. 다른 분들의 코드를 참고했었는데 다들 useEffect , setState가 정식이었다..

나만 이런건가 왜이런건가 생각하면서 계속 문제 해결방법에 대해 찾아보게 되었다.

그러다가 알게된 docChanges()!!

docChanges()

querySnapshot에서 docChanges()를 사용할수 있다. 이를 사용하게 되면
add나 modify, remove 등 새로운값이 추가될때, 수정될때 , 삭제될때 새로운 값들만 가져올수 있었다.

사용 방법은 기존의 querySnapshot을 forEach 돌리는 것에서 중간에 docChanges함수를 호출하면된다.

querySnapshot.docChanges().forEach((doc)=> )

이렇게 사용하면된다. doc.type === ‘added’ 등의 조건으로 값을 확인할수 있다.

하지만…

이를 사용해도 콘솔은 분명 한번씩 잘찍혔는데 값을 저장하기 위해 setState를 사용하니 무한루프가 계속 되었다.

고민고민하며 계속 코드를 수정해보고 노트에도 원인분석을 하기 위해 정리를 하면서 하나씩 도전했는데

결국 문제를 해결할수 있었다.

useEffect와 useState 때문에 문제가 발생한것이다 라고 생각했었는데

확실히 useEffect가 필요가 없었다.. 왜냐하면 querySnapshot 자체가 계속 소켓처럼 연결하고 있었기 때문..
querySnapshot 만 해두면 useEffect 상관없이 값이 변경될때마다 계속 값을 추가해준다.

그래서 수정된 코드는 바로 아래와 같다.

messagesRef.onSnapshot((querySnapshot) => {

// 기존 docChanges없는 방법 
    //   const data: ChatType[] = querySnapshot.docs.map((doc) => {
    //     console.log(doc.data(), "메시지");
    //     return {
    //       ...doc.data(),
    //       id: doc.id,
    //     };
    //   });
    //   setDocs(data);
    //   console.log(data);
    //   datas = data;

//해결 방법 
    querySnapshot.docChanges().forEach((doc) => {
      if (doc.type === "added") {
        // 새로운 것만 리슨
        if (docId.length && docId.filter((x) => x === doc.doc.id)) {
          // ID 값이 같은게 있을 경우 리턴
          return;
        } else {
          setDocId((prev) => [...prev, doc.doc.id]);
          const data = querySnapshot.docs.map((docs) => {
            return { ...docs.data(), id: docs.id };
          });
          setMessages(data);
          return;
        }
      } else if (doc.type === "removed") {
        console.log("removed");
      }
    });
  });

문제 해결은 다음과 같다. 계속 호출을 하길래 doc.id 값을 계속 보관하면서 doc.id 값이 계속 반복되면 함수를 return 하는 방식이다. filter 구문으로 만약 doc.id가 겹치면 리턴을 하게 함수를 실행했다.

useEffect도 과감히 제거했다. 그랬더니 하나씩 잘 불러오고 무한렌더링 없이 잘 해결할수 있었다.

이번 작업에서는 사수도 없이 혼자하는 작업이어서 문제해결에 검색에 의존을 많이 하게 되었다.
직접 코드도 머리를 쥐어짜며 계속 여러번 시도도 해보고 정해진 시간내에 구현을 해야한다는 압박감도
쉽지 않았다.
문제에 직면하며 어떻게 문제를 해결해야할지 답도 없을때 막막하기도 하였지만 문제를 해결하고 나니

별것도 아닌 코드 한줄로 문제가 해결되는 것을 보고 속이 시원하기도 하며 해냈다는 성취감을 얻을수 있었다.

이런 기분때문에 코딩을 하는 것이라고 생각이 든다.

profile
프론트엔드 개발자 초보에서 고수까지!

0개의 댓글