react, express, mysql, socket.io 를 이용해 채팅만들기(응용)

조승윤·2022년 7월 12일

처음에는 socket.io를 사용하고 채팅 내역은 state에 저장하는 간단한 채팅예제를 만들었다.
쉬는날 만지다 보니 채팅내역을 DB에도 저장하고싶고 인피니트 스크롤링도 거꾸로 구현하고싶고 스크롤링 될때마다 받아오는 데이터도 캐싱 처리를 하고싶었다.

서버

심심풀이 프로젝트이기 때문에 서버쪽은 정말 간단하게 구현하였다.
인피니티 스크롤링을 위해 페이지네이션 기능만 넣어주었다.
DB에는 간단하게 id, 방번호, 작성자, 메세지내용, 작성시간만 들어간다.

app.get("/get_msg", (req, res) => {
  let roomnum = req.query.roomnum;
  let rowPerPage = 30; // 페이지당 보여줄 글목록 : 30개 오프셋에 들어갈 값
  let currentPage = 1;
  if (req.query.page) {
    currentPage = parseInt(req.query.page);
  } /////// 정수로 바꿔주기
  let beginRow = (currentPage - 1) * rowPerPage; //받아오는 인덱스, 시작점 2페이지면 60부터 시작  3페이지면 90부터 시작
  connection.query("SELECT * FROM chat_study WHERE room = ? ORDER BY id DESC LIMIT ?,?", [roomnum, beginRow, rowPerPage], (err, row) => {
    if (err) {
      console.log(err);
    } else {
      res.json({ result: row, page: parseInt(req.query.page) });
    }
  });
});

Request URL: api/get_msg?roomnum=456&page=1

파라미터로 room번호와 page를 요청한다

요청 결과↓

클라이언트

이번에 만든 채팅의 주요 기능은 react query이다. react query를 이용해 데이터를 받아오고 캐싱처리를 한다.

이전 대화내역을 보여줄때 전체 내용을 한번에 받아오지않고 페이징 처리해 30개씩 가져온다.

스크롤링을 하면서 이전 대화내역을 보여주기위해 다음페이지를 요청하면 위와같이 pages라는 배열에 요청했던 데이터가 계속 쌓이게된다.

useInfiniteQuery

  const { isLoading, isError, error, data, fetchNextPage, isFetching, isFetchingNextPage, refetch, hasNextPage } = useInfiniteQuery(
    "chat_list",
    async ({ pageParam = 1 }) => {
      const res = await axios.get(`http://주소/get_msg` + `?roomnum=${room}&page=${pageParam}`); // room번호와 page를 전달
      return res.data;
    },
    {
      onSuccess: (data) => {
        console.log("성공", data);
      },
      refetchOnWindowFocus: false,
      staleTime: 3600000,
      cacheTime: 3600000,
      getNextPageParam: (lastPage) => lastPage.page + 1,
    }
  );

  if (isLoading) {
    return <h2>Loading...</h2>;
  }

  if (isError) {
    return <h2>{error.message}</h2>;
  }

data는 아래와 같이 뿌려주면된다

          {data?.pages
            .map((page) =>
              page.result.map((messageContent) => (
                <div className="message" key={messageContent.id} id={username === messageContent.author ? "you" : "other"}>
                  <div>
                    <div className="message-content">
                      <p>{messageContent.message}</p>
                    </div>
                    <div className="message-meta">
                      <p id="time">{moment(messageContent.time).format("HH:mm")}</p>
                      <p id="author">{messageContent.author}</p>
                    </div>
                  </div>
                </div>
              ))
            )}

인피니티 스크롤

인피니티 스크롤을 구현하기 위해 window.addEventListenner를 사용하여 페이지가 끝에 오는지를 판단하여 만드는 방법이 있다. 하지만 스크롤을 할 때마다 이벤트가 발생해 별로 사용하고 싶지 않았다.

그래서 나는 react-intersection-observer 를 사용하여 구현하였다. react query의 공식홈페이지 예제에서도 사용되었다.

IntersectionObserver는 대상이 화면에 보이면 콜백 함수를 실행하며 원하는 동작을 수행한다

const { ref, inView } = useInView(); // ref가 노출되면 true, false를 반환한다

  useEffect(() => {
    if (inView) {
      fetchNextPage();
    }
  }, [inView]); //inview가 노출되었을때 fetchNextPage()실행

html 부분은

 <button onClick={fetchNextPage} disabled={!hasNextPage || isFetchingNextPage} ref={ref}>
      더보기버튼
 </button>

끝 부분에 버튼을 만들어주고 ref를 할당해주었다. 스크롤을 올릴때 버튼이 노출되면 fetchNextPage()가 실행된다

채팅 전송과 수신

채팅을 전송하고 수신할때마다 db에 들어간 데이터 불러와 갱신하는 방식도 가능하지만 나는 과거에 했던 채팅 내역들은 useInfiniteQuery를 이용하여 불러오고 실시간으로 쌓이는 채팅내역들은 db에 저장하지만 화면에 보여지는건 usestate를 사용하기로했다. 채팅을 주고 받을때에는 socket.io와 usestate를 이용하여 화면에 보여지며 db에 채팅내역이 저장된다. 채팅방을 다시 들어오거나 새로고침해도 db에 쌓인 최신데이터 부터 불러오기 때문에 마지막까진 했던 채팅내역들이 보존된다.

메시지전송

  const send = async () => {
    if (Message !== "") {
      const messageData = {
        room: room,
        author: username,
        message: Message,
        time: new Date(Date.now()),
      };
      await socket.emit("send_message", messageData);
      set_history_data((list) => [...list, messageData]); //내가 보낸 메시지가 히스토리에 쌓이게함
      scrollRef.current.scrollTop = scrollRef.current.scrollHeight; // 채팅전송후 밑으로 스크롤 내려줌
      setMessage(""); //인풋창 초기화
    }
  };

메시지수신

  useEffect(() => {
    socket.on("receive_message", (data) => {
      console.log("서버에서 내려온 데이터", data);
      set_history_data((list) => [...list, data]); //받은 메시지가 히스토리에 쌓임
    });
  }, [socket]);

채팅 전송후 스크롤 맨 밑으로 내리기

메시지 컨테이너를 만든뒤

<div className="메시지 컨테이너(스크롤용)" ref={scrollRef}><div>

scrollRef를 할당한다. 메시지를 보낸후

scrollRef.current.scrollTop = scrollRef.current.scrollHeight;

스크롤을 컨테이너 제일 밑으로 내려준다.

0개의 댓글