프로젝트 마감일이 일주일 좀 넘게 남았을 때, 채팅 페이지를 만들기 시작했다.
주제가 지역 기반 심부름 커뮤니티인 만큼 심부름 의뢰인과 심부름 수행인 사이에 공개적이지 않은 의사소통 공간이 필요했는데, 처음엔 비밀댓글 형식으로 만들려다가 댓글은 새로고침하지 않는 이상 자동으로 업데이트되지 않으니 잦은 상호작용이 필요한 경우 실시간 채팅방 형식이 더 알맞은 것 같다는 데 의견이 모였다.
문제는 실시간 채팅 기능을 어떻게 구현하느냐였다. 검색을 하고 자료를 찾아본 결과 보통 실시간 기능은 웹소켓 프로토콜을 이용하여 개발하는 것 같았다. 하지만 시간이 일주일 정도밖에 남지 않은 상황에서 새로운 기술을 공부하고 적용한다는 것이 서버 쪽도 클라이언트 쪽도 부담이 되었다. 특히 웹소켓으로 주고받은 채팅 내용을 데이터베이스에 저장하는 로직에 대해 고민이 많았다. 그래서 다른 방법을 찾아보았고, 웹소켓이 등장하기 전 양방향 통신을 위해 사용되었던 폴링(Polling)이라는 방식에 대해 알게 되었다.
폴링(Polling)이란 클라이언트에서 일정 주기마다 서버에 (GET) 요청을 보내 실시간 통신처럼 보이게 하는 방식이다. 비교적 구현이 쉽다는 장점이 있지만, 필요하지 않은 상황에서도 지속적으로 무거운 http의 header가 오고 가기 때문에 서버에 부담을 줄 수 있다는 단점이 있다.
아래는 폴링 방식으로 구현한 채팅 페이지의 코드이다. 스타일과 관련된 부분은 생략하였다.
import { useState, useEffect, useRef } from "react";
import { useParams } from "react-router-dom";
// 유저 인증이 필요한 요청인 경우 사용하려고 만든 axios 인스턴스
import { authAxios } from "../api/core/instance";
export default function ChatPage() {
const chatContainerRef = useRef();
const { id } = useParams();
// 화면에 보여질 채팅 리스트
const [list, setList] = useState([]);
// 서버에 POST 요청으로 보낼 채팅 데이터
const [chat, setChat] = useState({
applyId: id,
content: "",
});
// 이 부분이 지속적으로 GET 요청을 보내는 폴링 부분!
useEffect(() => {
const fetchData = async () => {
const response = await authAxios.get(`/chats/${id}`);
setList(response.data);
};
fetchData(); // 맨처음 랜더링 시 실행
const interval = setInterval(fetchData, 5000); // 그후 5초마다 실행
return () => clearInterval(interval);
}, []);
useEffect(() => {
// 항상 최근 채팅이 보이도록 하기 위해 리스트 갱신 시 스크롤 아래로 보내기
const scrollToBottom = () => {
if (chatContainerRef.current) {
chatContainerRef.current.scrollTop = chatContainerRef.current.scrollHeight;
}
};
scrollToBottom();
}, [list]);
const handleChange = event => {
setChat(previous => ({ ...previous, content: event.target.value }));
};
const handleSubmit = async event => {
event.preventDefault();
try {
const response = await authAxios.post("/chats", chat);
const newMessage = response.data;
setList(prevList => [...prevList, newMessage]);
event.target.reset();
setChat(previous => ({ ...previous, content: "" }));
} catch (error) {
console.log(error);
alert("메시지 전송에 실패하였습니다.");
}
};
return (
<div>
<div>
{list && list.length > 0 ? (
<div ref={chatContainerRef}>
{list?.map((message, index) => (
<div key={index}>
<span>{message.nickName} :</span>
<span>{message.content}</span>
</div>
))}
</div>
) : (
<div>메시지가 없습니다.</div>
)}
<form onSubmit={handleSubmit}>
<input
type="text"
value={chat.message}
onChange={handleChange}
placeholder="메시지를 입력하세요"
required
/>
<button type="submit">Send</button>
</form>
</div>
</div>
);
}
이렇게 기한 안에 목표로 한 기능을 완성해서 정말 만족스러웠다. 하지만 구현 화면을 보면 알 수 있듯이 타이밍이 잘 안 맞으면 좀 답답한 느낌이 있고 (요청 보내는 주기를 줄이면 되지만 서버에 부담이 갈 수 있어 5초로 설정해 둔 상태), 엄밀히 말하자면 이 방식은 양방향 통신이 아니기 때문에 조금 아쉬운 부분이 있었다. 다음에는 웹소켓을 공부해서 실시간 기능 구현에 적용해 볼 수 있도록 해야겠다.
→ 구현해 보았다!
웹소켓을 이용하여 실시간 채팅 구현하기 [React / STOMP]
약 3개월 전쯤 지역 기반 심부름 커뮤니티 서비스를 만들면서 채팅방을 구현했던 적이 있다. 당시 여러 번의 회의를 거쳐 서비스의 특성이나 기술적인 어려움 등의 이유로, 웹소켓이 아닌 일반 A
ddppp.tistory.com
'[ 프로젝트 ] > 빨간 망토' 카테고리의 다른 글
웹에디터 이미지 업로드 훅 커스텀하기 (TOAST UI, 클라우디너리) (0) | 2023.06.18 |
---|---|
모달/탭 UI 구현하기 (0) | 2023.06.14 |
메인 프로젝트 - 지역 동네 기반 심부름 커뮤니티 🔴빨간 망토🔴 (0) | 2023.06.06 |
댓글